前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析


  Promise其实也不难-****博客

Promise 的执行顺序分析

Promise 对象的执行是异步的,但其执行器函数内部的代码是立即执行的,而 then方法注册的回调函数则是在 Promise 状态改变后执行的

const myPromise = new Promise((resolve, reject) => {
  console.log('A');
  console.log('B');
});

myPromise.then(() => {
  console.log('C');
});

console.log('D');

A-》B-》D

const newPromise1 = new Promise((resolve, reject) => {
  console.log('A');
  resolve('B');
});
const newPromise2 = newPromise1.then(res => {
  console.log(res);
});
console.log('C', newPromise1);
console.log('D', newPromise2);

A-》C Promise{<fulfilled>:'B'}->D Promise{<pending>}->B

const newPromise = new Promise((resolve, reject) => {
  console.log('A');
  setTimeout(() => {
    console.log("timer start");
    resolve("succeed");
    console.log("timer end");
  }, 0);
  console.log('B');
});
newPromise.then((result) => {
  console.log(result);
});
console.log('C');

事件循环

A-》B-》C-》timer starter->timer end ->succeed

流程解读


  • 当执行到new Promise时,会立即执行Promise构造函数中的执行器函数(executor function),即传递给Promise构造函数的箭头函数
  • 在执行器函数内部,首先打印出'A'
  • 然后,setTimeout被调用,它设置了一个延时为0毫秒的定时器回调。由于setTimeout的回调被放入了宏任务队列(macrotask queue),它不会立即执行,而是等待当前执行栈清空并且微任务队列(microtask queue)也为空时,才会被执行
  • 紧接着,打印出'B'
  • 此时,Promise的执行器函数执行完毕,但Promise对象newPromise状态仍然是pending,因为resolve函数是在setTimeout的回调中调用的,而这个回调还没有执行
  • 紧接着,打印出'C'
  • 事件循环和微任务队列:到目前为止,执行栈已经清空,继续执行微任务队列、宏任务队列中的任务,执行setTimeout()内回调函数。
  • resolve方法会触发在微异步任务队列then()方法,因此先输出栈中数据timer end,再检查队列中待执行的任务resolve

事件循环(Event Loop):事件循环是JavaScript运行时环境中的一个循环机制,它不断地检查调栈用和任务队列。当调用栈为空时,事件循环会首先检查微任务队列,并执行其中的所有任务。只有当微任务队列为空时,事件循环才会检查任务队列,并执行其中的任务。(调用栈-》微任务队列-》宏任务队列)

  • 微任务队列:它专门用于处理如Promiseresolvereject回调、async/await等微任务。微任务的优先级高于宏任务。
  • 宏任务队列:用来存储准备好执行的回调函数,比如setTimeoutsetInterval的回调
  • 调用栈

前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析_事件循环

编辑

Promise.resolve().then(() => {
  console.log('outerPromise');
  const innerTimer = setTimeout(() => {
    console.log('innerTimer')
  }, 0)
});

const timer1 = setTimeout(() => {
  console.log('outerTimer')
  Promise.resolve().then(() => {
    console.log('innerPromise')
  })
}, 0)
console.log('run');

run-》outerPromise-》outerTimer-》innerPromise-》innerTimer

前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析_事件循环_02

编辑

前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析_任务队列_03

编辑

then方法里面的return语句讲状态改为fulfilled! 

Promise.resolve().then(() => {
  return new Error('error')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

//打印出then: Error: error

 TypeError: Chaining cycle detected for promise

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
//输出TypeError: Chaining cycle detected for promise

promise异常处理

.catch(err=>{console.log(err)}) | .then(res)=>{console.log(res)},(err)=>{console.log(err)}

两个都可以用于处理,那个在先就先处理错误!!!!

Promise.reject('error')
  .then((res) => {
    console.log('succeed', res)
  }, (err) => {
    console.log('innerError', err)
  }).catch(err => {
    console.log('catch', err)
  })

innerError error

  1. Promise.reject('error'):创建一个被拒绝的 Promise,拒绝原因为 'error'
  2. .then((res) => { console.log('succeed', res) }, (err) => { console.log('innerError', err) })
  • 第一个回调函数(处理成功情况的)不会被调用,因为这个 Promise 已经被拒绝。
  • 第二个回调函数(处理拒绝情况的)会被调用,因为它专门用于处理 Promise 被拒绝的情况。因此,会执行 console.log('innerError', err),其中 err 的值为 'error'
  1. .catch(err => { console.log('catch', err) })
  • .catch() 方法是 .then(null, rejectionHandler) 的语法糖,专门用于捕获 Promise 链中未被处理的拒绝情况。
  • 然而,在这个例子中,.then() 方法已经包含了一个处理拒绝的回调函数,因此 .catch() 方法不会被调用。

console.log('catch', err) 也不会被执行, .then() 方法中已经包含了处理拒绝的逻辑


promise .finally()用法

被解决(fulfilled)或拒绝(rejected)之后,执行一些不依赖于 Promise 结果的代码。这个方法返回在JavaScript中,Promise.finally() 方法是一个非常有用的工具,它允许你在一个 Promise 对象一个新的 Promise该 Promise 的解决值(fulfilled value)或拒绝原因(rejection reason)与原始 Promise 相同



let promise = new Promise((resolve, reject) => { // 模拟一个异步操作 setTimeout(() => { resolve('操作成功'); // 如果改为 reject('操作失败'); 则会触发 catch 回调 }, 1000); }); promise .then(result => { console.log(result); // 操作成功 }) .catch(error => { console.error(error); // 如果Promise被拒绝,则会打印错误信息 }) .finally(() => { console.log('清理资源或执行其他不依赖于结果的代码'); // 这里可以执行一些清理工作,比如关闭数据库连接、清除缓存等 });


Promise.resolve('A')
  .then(res => {
    console.log('promise1', res)
  })
  .finally(() => {
    console.log('finally1')
  })
Promise.resolve('B')
  .finally(() => {
    console.log('finally2')
    return 'result'
  })
  .then(res => {
    console.log('promise2', res)
  })

//打印promise1 A、finally1、finally2、promise2 B

promise.all()方法(&&)

Promise.all() 是 JavaScript 中一个非常有用的方法,它接受一个包含多个 Promise 对象(或可迭代对象,如数组)的可迭代对象,并返回一个新的 Promise。这个新的 Promise 会在所有给定的 Promise 对象都成功解决(fulfilled)后解决,其解决值(fulfilled value)是一个数组,包含了所有原始 Promise 对象的解决值(按相同的顺序)。如果任何一个 Promise 对象被拒绝(rejected),则新的 Promise 会立即被拒绝,其拒绝原因(rejection reason)是第一个被拒绝的 Promise 的拒绝原因。

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    // 所有 Promise 都成功解决后执行这里的代码
    // “!!!values 是一个数组”,包含了 promise1, promise2, promise3 的解决值
  })
  .catch((error) => {
    // 如果有任何一个 Promise 被拒绝,则执行这里的代码
    // error 是第一个被拒绝的 Promise 的拒绝原因
  });

function runAsync(num) {
  return new Promise((resolve) => setTimeout(
    () => resolve(num, console.log(num)), 1000)
  );
}

function runReject(num) {
  return new Promise((resolve, reject) => setTimeout(
    () => reject(`Error: ${num}`, console.log(num)), 1000 * num)
  );
}

Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then((res) => console.log(res))
  .catch((err) => console.log(err));

  • 1s后 runAsync(1)、runAsync(3)输出1、3
  • 2s号 runReject(2)输出2,状态为reject,promise.all()捕获错误,执行.catch() Error 2
  • 4s后runAsync(4)输出4

promise.race()方法[谁早听谁的)]

Promise.race 是 JavaScript 中 Promise 对象的一个静态方法。它接收一个可迭代对象(比如数组)作为参数,该可迭代对象包含多个 Promise 对象(或者非 Promise 值,这些值会被隐式地转换为已解决的 Promise)。Promise.race 方法会返回一个新的 Promise,这个新的 Promise 的状态(解决或拒绝)是由第一个解决(resolve)或拒绝(reject)的传入的 Promise 决定的。

function runAsync(num) {
  return new Promise(
    (resolve) => setTimeout(() => resolve(num, console.log(num)), 1000)
  );
}

function runReject(num) {
  return new Promise(
    (resolve, reject) =>
    setTimeout(() => reject(`Error: ${num}`, console.log(num)), 1000 * num),
  );
}

Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then((res) => console.log('res: ', res))
  .catch((err) => console.log(err));

前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析_回调函数_04

编辑

function runAsync(num) {
  return new Promise(
    (resolve) => setTimeout(
      () => resolve(num, console.log(num)), 1000
    )
  );
}

Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then((res) => console.log('res: ', res))
  .catch((err) => console.log(err));

前端代码分析题(选择题、分析题)——JS事件循环分析、await和作用域分析_事件循环_05

编辑

async与await 

async 和 await 是 JavaScript 中用于处理异步操作的关键字,它们提供了一种更直观、更易于理解的异步编程方式。在底层,async 和 await 是基于 Promise 实现的,它们让异步代码看起来像是同步代码一样。

async 和 await 底层详解

  1. async 函数
  • 当一个函数被声明为 async 时,它会隐式地返回一个 Promise。
  • async 函数内部可以使用 await 关键字来等待其他 Promise 的解决。
  • 如果 async 函数内部没有显式地返回一个 Promise,那么它会返回一个已经解决的 Promise,其值为 undefined
  • 如果 async 函数内部抛出了一个异常,那么这个异常会被封装成一个被拒绝的 Promise。
  1. await 关键字
  • await 只能在 async 函数内部使用。
  • await 会暂停 async 函数的执行,直到它等待的 Promise 被解决或拒绝
  • 如果 await 的 Promise 被解决,那么 await 表达式的结果就是 Promise 的解决值。
  • 如果 await 的 Promise 被拒绝,那么 async 函数会立即抛出一个异常,这个异常可以被 try...catch 语句捕获。
  • await 不会阻塞整个事件循环,它只是暂停了 async 函数的执行,允许其他代码继续运行。
async function runAsync() {
  console.log("runAsync start");
  await asyncFunc();
  console.log("runAsync end");
}

async function asyncFunc() {
  console.log("do something");
}

runAsync();
console.log('start')

  • runAsync start
  • do something
  • start
  • runAsync end

同步代码的执行(start执行时机分析

  • 在 runAsync 被调用的同时(由于它是异步的),同步代码 console.log('start') 也被立即执行,打印 "start"
  • 重要的是要注意,虽然 runAsync 包含了异步操作,但它被调用后,控制权立即返回到事件循环,允许同步代码(如 console.log('start'))在 runAsync 的异步操作完成之前执行。

await仅阻塞当前函数不阻塞其他函数

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('async1 timer')
  }, 0)
}

async function async2() {
  console.log("async2 start");
  setTimeout(() => {
    console.log('async2 timer')
  }, 0)
  console.log("async2 end");
}

async1();
setTimeout(() => {
  console.log('outer timer')
}, 0)
console.log("run")

  • async1 start
  • async2 start
  • async2 end
  • run
  • async1 end
  • async2 timer
  • outer timer
  • async1 timer

 await堵塞当前函数,等promise被resolve或reject才执行后续代码

async function runAsync () {
  console.log('async start');
  await new Promise(resolve => {
    console.log('promise')
  })
  console.log('async end');
  return 'async result'
}
console.log('main start')
runAsync().then(res => console.log(res))
console.log('main end')

  • main start
  • async start
  • promise
  • main end

此await后promise未被resolve或reject,故runAsync后续函数不会被执行

await只关心它直接等待的那个 Promise 的状态

 await 表达式并不关心 .then() 返回的新 Promise 的状态;

它只关心它直接等待的那个 Promise 的状态。 

async function runAsync () {
  console.log('async start');
  await new Promise(resolve => {
    console.log('promise')
    resolve('promise resolve')
  }).then(res => console.log(res))
  console.log('async end');
  return 'async result'
}
console.log('main start')
runAsync().then(res => console.log(res))
console.log('main end')

  •  main start
  • async start
  • promise
  • main end
  • promise resolve
  • async end
  • async result

await后Promise被reject需处理错误

如果 await 的 Promise 被拒绝,那么 async 函数会立即抛出一个异常,这个异常可以被 try...catch 语句捕获,未捕获直接报错(UnhandledPromiseRejectionWarning:error)

async function runAsync () {
  await promiseFunc();
  console.log('async');
  return 'async result'
}

async function promiseFunc () {
  return new Promise((resolve, reject) => {
    console.log('promise')
    reject('error')
  })
}

runAsync().then(res => console.log(res))

仅仅输出promise,未捕获直接报错

  • process.nextTick()事件循环时是加入微任务队列 
  • promise中resolve()函数后代码也需执行
  • setTimeout也需考虑可能题目中不同函数的延迟时间不同

作用域分析

区分严格模式和非严格模式,this不同,严格模式this为undefined

箭头函数的this不可用apply|call等方法更改

var num = 10
var obj1 = {
  num: 20,
  print: () => {
    console.log(this.num)
  }
}
obj1.print()

var obj2 = { num: 30 }
obj1.print.apply(obj2)
//输出
//10
//10

 浏览器环境调用function.call(null),this指向全局对象window

function test() {
  console.log(this);
}
test.call(null);

new关键字

var obj = { 
  user: 'yupi', 
  print: function(){ 
    console.log(this.user); 
  } 
} 
obj.print()
new obj.print()

  • yupi
  • undefined(非严格模式),TypeError严格模式

new obj.print():这里的代码尝试使用 new 关键字来调用 obj 对象的 print 方法。但是,这里有一个重要的概念需要理解:new 关键字用于构造对象实例,它期望后面的调用是一个构造函数。

  • 创建新对象:当 new 关键字执行时,它会创建一个新的空对象,并将这个新对象的原型设置为构造函数的 prototype
  • 设置原型链:JavaScript引擎会将新创建的对象的内部原型(__proto__)设置为构造函数的prototype属性
  • 绑定this并执行构造函数:然后,JavaScript引擎会将构造函数作为普通函数调用,但此时this关键字的值会被设置为新创建的对象。这意味着在构造函数内部,你可以通过this来引用新对象,并向其添加属性和方法。构造函数中的代码会执行,可能会初始化对象的属性、调用其他方法等。
  • 返回新对象:构造函数执行完毕后,如果它没有显式地返回对象(非原始值),则 new 表达式会默认返回这个新创建的对象。

然而,obj.print 并不是一个构造函数,而是一个普通的方法。它并没有设计为返回一个对象(实际上它返回的是 undefined,因为它没有 return 语句)。此外,它也没有一个 prototype 属性(这是构造函数才有的)。因此,使用 new 来调用它并不是一个好的做法,并且可能会导致意外的行为或错误。

严格模式下尝试使用 new 来调用一个非构造函数会导致 TypeError

但在非严格模式下,JavaScript 引擎会尝试执行这个操作,但结果通常不是你所期望的。


**与此类似,箭头函数不能做构造函数**

  1. 没有this绑定
    箭头函数不绑定自己的this,它会捕获其所在上下文的this值作为自己的this值,并且这个绑定在函数创建时就已经确定了,之后不会改变。这意味着在箭头函数内部使用this时,它总是引用函数被定义时的上下文中的this,而不是函数被调用时的上下文。这与构造函数的行为不符,因为构造函数需要在被new调用时能够正确地绑定到新创建的对象上。
  2. 没有prototype属性
    构造函数有一个特殊的prototype属性,这个属性是一个对象,它包含了可以由构造函数的所有实例共享的属性和方法。当你使用new关键字调用构造函数时,新创建的对象会继承这个prototype对象。然而,箭头函数没有prototype属性,因此它们不能被用作构造函数来创建具有共享原型方法和属性的对象实例。
  3. 不能使用new关键字调用
    由于箭头函数没有prototype属性,并且它们的this绑定是固定的,因此尝试使用new关键字来调用箭头函数会导致错误。JavaScript引擎会抛出一个TypeError,指出箭头函数不是构造函数



上一篇:书生大模型实战营第四期-入门岛-1. Linux前置基础