请教一个JS事件循环的问题

nero
  • 3.9k

代码如下:

  function request() {
    return new Promise(resolve => {
      resolve('request')
    })
  }

  console.log('script start')
  setTimeout(() => {
    console.log('setTimeout')
  })
  Promise.resolve().then(() => {
    console.log('promise 1')
  }).then(() => {
    console.log('promise 2')
  })
  async function foo() {
    await request()
    console.log('req end')
  }
  foo()
  console.log('script end')

打印的结果如下:
image.png

不明白的点在于,为什么req end 夹在promise1 和 promise2之间打印出来了

而如果换成这样:

  console.log('script start')
  setTimeout(() => {
    console.log('setTimeout')
  })

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

  async function foo() {
    await fetch('https://randomuser.me/api')
    console.log('req end')
  }
  foo()
  
  console.log('script end')

打印的结果就是这样的:
image.png

为什么呢?和fetch有关吗?

回复
阅读 395
3 个回答
✓ 已被采纳
不明白的点在于,为什么req end 夹在promise1 和 promise2之间打印出来了

你这个问题可以解释为Promise.then回调的执行顺序
Promises/A+规范
举个例子:

  const promise1 = new Promise((resolve, reject) => {
    console.log('promise 1')
     resolve();
   });
   
   promise1.then(() => {
    console.log('promise 2')
   });
   
   promise1.then(() => {
    console.log('req end')
   })

打印结果为:promise 1、promise 2、req end。
解释为

then 方法可以被同一个 promise 调用多次
当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调

再看个你这个例子

 function request() {
    return new Promise(resolve => {
      resolve('request')
    })
  }
  Promise.resolve().then(() => {
    console.log('promise 1')
  }).then(() => {
    console.log('promise 2')
  })
  async function foo() {
    await request()
    console.log('req end')
  }
  foo()

打印结果为:promise 1、req end、promise 2。
我们先看下概念

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
如果 x 不为对象或者函数,以 x 为参数执行 promise

当执行完console.log('promise 1')并打印promise 1后,执行完了,没有返回东西,可以理解为返回undefined ,用x完成(fulfill)promise,这个时候把console.log('promise 2')放到微任务等待执行。
继续走,遇到 async await,等待request()执行完,继续走,打印eq end。所有同步任务完成,执行微任务里的console.log('promise 2')

js是单线程的, 它有一个事件循环
查资料看, setTimeout(队列名为macro宏)和async(队列名为micro微)属于两个事件队列
执行顺序是

  1. macro 执行一个事件
  2. 执行全部micro中的事件

await 又想当于同步执行, 就是在await这条语句的地方, 先执行 await 的参数, 执行完之后才往await的下一行走.


  
  function request() {
    // promise 一初始化, 就直接执行参数这个callback
    // callback 里面又直接 resolve, 所以此处是同步代码
    return new Promise(resolve => {
      resolve('request')
    })
  }
  // 直接输出 (1)
  console.log('script start')
  // 函数进macro队列 (2)
  setTimeout(() => {
    // 执行 macro队列事件 (13)
    console.log('setTimeout')
  })
  // 进 micro 队列 (3)
  Promise.resolve().then(() => {
    // 执行micro事件 (7)
    console.log('promise 1')
    // 完成后, 下面这个函数进 micro 队列 (8)
  }).then(() => {
    // 执行 micro 事件 (11)
    console.log('promise 2')
    // 到此为止, 所有micro事件执行完成 (12)
  })
  async function foo() {
    // 执行micro事件 (9) 因为有await, 相当同步执行 request()
    // await fetch() 这行, fetch 是异步代码, 应该是把事件加到macro队列里面去了, 等执行完成 之后才调用 resolve, 所以会最后显示 .
    await request()
    // 因为 request中不是异步代码, 所以直接返回, 往下走 (10)
    console.log('req end')
  }
  // 执行foo, 但 foo是async, 异步, 所以是进 micro 队列 (4)
  foo()
  
  // 直接输出 (5)
  console.log('script end')
  
  // 到此, macro 队列事件完成一次, 下面执行 micro 队列的事件 (6)

说下上面的
1、执行console.log('script start'),打印script start
2、执行setTimeout,把console.log('setTimeout')加入宏任务
3、执行Promise.resolve(),把console.log('promise 1')加入微任务队列
4、执行foo --> 执行request --> 执行new Promise() --> 执行resolve('request'),把console.log('req end')加入微任务队列
5、执行console.log('script end'),打印script end
6、宏任务结束,执行微任务队列
7、执行console.log('promise 1'),打印promise 1,把console.log('promise 2')加入微任务队列
8、执行console.log('req end'),打印req end
9、执行console.log('promise 2'),打印promise 2
10、微任务队列清空,执行宏任务队列
11、执行console.log('setTimeout'),打印setTimeout

下面的主要区别在于fetch是网络请求,需要等请求结束

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏