返回

乍看之下很正常,背后隐藏的异步执行结果揭秘:JS中那些让你意想不到的异步行为

前端

引言

我们都听说过JavaScript中的异步编程,但你真的理解它背后的工作原理吗?本文将带你深入了解异步代码执行的机制,并通过几个令人意想不到的示例来揭示它的奥秘。

异步编程的基本原理

在JavaScript中,代码的执行顺序是由事件队列决定的。事件队列是一个先入先出的队列,它存储了所有需要执行的任务。当主线程空闲时,它就会从事件队列中取出任务并执行。

事件队列中的任务分为两种类型:宏任务和微任务。宏任务包括脚本、setTimeout()和setInterval()等,而微任务包括Promise和MutationObserver等。

微任务和宏任务的区别

微任务和宏任务的区别在于它们的执行时机。微任务会在当前宏任务执行完成后立即执行,而宏任务则会在当前宏任务以及所有微任务执行完成后执行。

几个意想不到的异步执行结果

下面是一些常见的异步代码执行结果,它们可能会让你大吃一惊:

  • 示例一:Promise的回调函数在同步代码中执行
const promise = new Promise((resolve, reject) => {
  console.log('promise1');
  resolve('success');
});

promise.then((result) => {
  console.log(result); // 'success'
});

console.log('script end');

在这个示例中,我们创建了一个Promise,并在其内部输出'promise1'。然后,我们调用then()方法,并在回调函数中输出Promise的结果。最后,我们在脚本的最后一行输出'script end'。

如果你认为脚本的输出应该是'promise1'、'success'、'script end',那么你错了。实际的输出顺序是'promise1'、'script end'、'success'。

这是因为Promise的回调函数是在当前宏任务执行完成后执行的,而不是在Promise的状态改变后立即执行。因此,当主线程执行到最后一行脚本时,Promise的回调函数还没有执行,所以'script end'会被先输出。

  • 示例二:setTimeout()的回调函数在微任务之前执行
setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('promise');
});

console.log('script end');

在这个示例中,我们创建了一个setTimeout,并在其回调函数中输出'setTimeout'。然后,我们创建了一个Promise,并在其then()方法的回调函数中输出'promise'。最后,我们在脚本的最后一行输出'script end'。

如果你认为脚本的输出应该是'script end'、'promise'、'setTimeout',那么你又错了。实际的输出顺序是'script end'、'setTimeout'、'promise'。

这是因为setTimeout()的回调函数是在当前宏任务执行完成后执行的,而Promise的回调函数是在当前宏任务以及所有微任务执行完成后执行。因此,当主线程执行到最后一行脚本时,setTimeout()的回调函数还没有执行,所以'script end'会被先输出。

结论

通过以上几个示例,我们可以看到,在JavaScript中,异步代码的执行顺序并不总是我们想象的那样。如果你想写出更健壮可靠的程序,就需要深入理解异步代码执行的机制。