JavaScript同步异步代码执行顺序疑问:日志顺序解析
以下是一段TypeScript代码及其执行日志,针对输出顺序的疑问逐一解答:
代码示例
console.log("-------------------------- (LOG 1)"); setTimeout(() => console.log("-----------------------------(LOG 2)"), 1000); const fetchedData = fetch("https://pokeapi.co/api/v2/pokemon/ditto"); const fnReturningNewPromise = () => { return new Promise<string>((resolve, reject) => { if (1 == 1) { console.log("LOG 3 - new Promise in return Statement"); resolve("fnReturningNewPromise - resolved"); } reject("fnReturningNewPromise - rejected"); }); }; const customPromise = new Promise<string>((resolve, reject) => { if (1 === 1) { console.log("LOG 4 - new Promise"); resolve("Promise resolved"); } else { reject("REJECTED"); } }); fetchedData .then((res) => { console.log("LOG 5 - fetchedData 1st then - res.json()"); return res.json(); }) .then((data) => console.log("LOG 6 - with fetching")); Promise.resolve(1).then((res) => console.log("LOG 7 - immidielty resolved", res) ); customPromise .then((response) => response) .then((data) => console.log("LOG 8 - without fetching")); console.log("LOG 9 - sync code"); setTimeout(() => { console.log("LOG 10 - setTimeout - immidietly resolved"); }, 0); fnReturningNewPromise().then((res) => console.log("LOG 11 - resolving promise returned from fn: ", res) );
执行日志
-------------------------- (LOG 1) LOG 4 - new Promise LOG 9 - sync code LOG 3 - new Promise in return Statement LOG 7 - immidielty resolved 1 LOG 11 - resolving promise returned from fn: fnReturningNewPromise - resolved LOG 8 - without fetching LOG 10 - setTimeout - immidietly resolved LOG 5 - fetchedData 1st then - res.json() LOG 6 - with fetching -----------------------------(LOG 2)
疑问解答
1. 纯同步代码LOG 9为何在LOG 4之后输出?
new Promise()的执行器函数(即传入的(resolve, reject) => {...})是同步执行的。代码执行到const customPromise = new Promise(...)时,会立刻调用执行器函数,里面的console.log("LOG 4 - new Promise")属于同步代码,直接输出。而console.log("LOG 9 - sync code")写在customPromise定义之后,因此LOG 4先输出,LOG 9后输出。
2. 既然LOG 4是Promise构造函数内的同步代码,为何函数返回的Promise内的LOG 3未在LOG 4之前输出?
fnReturningNewPromise是一个函数,只有主动调用它时,内部的new Promise()才会执行。代码中fnReturningNewPromise()的调用在console.log("LOG 9 - sync code")之后,所以它内部的LOG 3自然在LOG 4之后输出——LOG 4是定义customPromise时同步执行的,而LOG 3要等到函数被调用才会触发。
3. LOG7、LOG11、LOG8的顺序为LOG7→LOG11→LOG8,为何关联LOG4的LOG8晚于关联LOG3的LOG11?
微任务队列按添加顺序执行,拆解顺序如下:
- 执行
Promise.resolve(1).then(...)时,对应的回调被加入微任务队列,这是第一个微任务(对应LOG7)。 - 执行
customPromise.then(...).then(...)时,第一个then(response => response)被加入微任务队列(第二个微任务);只有当这个微任务执行完毕,返回的resolved Promise的第二个then(对应LOG8)才会被加入微任务队列。 - 调用
fnReturningNewPromise()时,同步输出LOG3,随后这个Promise立刻resolve,它的then回调(对应LOG11)被加入微任务队列(第三个微任务)。
同步代码执行完毕后,微任务队列的顺序为:LOG7的回调 → LOG11的回调 → LOG8的回调。因此执行顺序是LOG7→LOG11→LOG8。
4. setTimeout属于宏任务,优先级低于Promise微任务,为何LOG10未在最后(LOG2之前)输出?
宏任务队列同样按添加顺序执行:
- 第一个
setTimeout(1000ms延迟)先被加入宏任务队列。 - 第二个
setTimeout(0ms延迟)后被加入宏任务队列。
当所有微任务执行完毕后,事件循环会处理宏任务队列,先执行0ms延迟的setTimeout(LOG10),再等待1000ms后执行第一个setTimeout(LOG2)。而fetch的回调(LOG5、LOG6)需要等待网络请求完成后才会加入微任务队列,它的触发晚于0ms的setTimeout——因为网络请求需要耗时,在LOG10触发时,fetch的响应还未返回,因此LOG10会在LOG5、LOG6之前输出,等fetch响应返回后才会执行其then回调。
内容的提问来源于stack exchange,提问作者TheCodemate

