如果 event loop 又搞不清楚就来看这篇文章

164 阅读4分钟

名词解释

EventLoop: 事件轮询就是解决javaScript单线程对于异步操作的一些缺陷,让 javaScript做到既是单线程,又绝对不会阻塞的核心机制,是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制。 宏任务和微任务:在任务队列(queue)中注册的异步回调又分为两种类型,宏任务和微任务。我们为了方便理解可以认为在任务队列中有宏任务队列和微任务队列。宏任务队列有多个,微任务只有一个。

  • 宏任务(macro Task) (在node环境中多了setImmediate,此时优先级setTimeout = setInterval(看先后) > setImmediate)

    script(整体代码)

    setTimeout/setInterval

    setImmediate(Node环境)

    UI 渲染

    requestAnimationFrame

    ....

  • 微任务(micro Task) (node环境下有 process时,在微任务队列中有 process的话一定是最先执行的优先级最高)

    process.nextTick(Node 环境)

    Promise的then()、catch()、finally()里面的回调

    ...

EL执行顺序

用个人的理解来阐述:

1.先从同步代码执行(可以看出第一次的宏任务),如果遇到同步代码立即执行,遇到异步代码就放到任务队列里,任务队列又分为宏任务队列和微任务队列。

2.执行完同步代码后,查看微任务队列是否有任务,有的话执行清空微任务队列,如果有微任务产生的微任务也一并清空。但是要注意的是如果是执行微任务而产生的微任务要放到微任务的队列末尾,是有顺序的。·

3.看完执行完微任务队列后,再看看宏任务队列,有的话执行,没有的话第一波EL结束。执行过程中如果产生了微任务就将其放到微任务队列汇中,完成宏任务之后再去执行清空微任务。 如此继续反复,再去看宏任务队列

题目练手

这题考察的点在 await 后面的代码 要放到微任务队列里

console.log('script start');

setTimeout(() => {
  console.log('北歌');
}, 1 * 2000);

Promise.resolve()
.then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});


async function foo() {
  await bar()
  console.log('async1 end')
}
foo()

async function errorFunc () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
  console.log('async1');
  return Promise.resolve('async1 success')
}
errorFunc().then(res => console.log(res))

function bar() {
  console.log('async2 end') 
}

console.log('script end');

这题考察的点在 宏任务有两个,要分别执行,还有对于宏任务里面的微任务的处理

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  })
  new Promise((resolve) => {
    console.log('4');
    resolve();
  }).then(() => {
    console.log('5')
  })
})

Promise.reject().then(() => {
  console.log('13');
}, () => {
  console.log('12');
})

new Promise((resolve) => {
  console.log('7');
  resolve();
}).then(() => {
  console.log('8')
})

setTimeout(() => {
  console.log('9');
  Promise.resolve().then(() => {
    console.log('10');
  })
  new Promise((resolve) => {
    console.log('11');
    resolve();
  }).then(() => {
    console.log('12')
  })
})

这道题的考点在于微任务的队列怎么排放

new Promise((resolve, reject) => {
  console.log(1)
  resolve()
})
.then(() => {
  console.log(2)
  // 如果这里是return 的话,结果又不一样了 return new Promise...时 得把里面的所有的.then 清干净,在考虑 console.log(6)的最外层.then
  new Promise((resolve, reject) => {
      console.log(3)
      setTimeout(() => {
        console.log(10)
      }, 3 * 1000);
      resolve()
  })
    .then(() => {
      console.log(4)
      new Promise((resolve, reject) => {
          console.log(5)
          resolve();
      })
        .then(() => {
          console.log(7)
        })
        .then(() => {
          console.log(9)
        })
    })
    .then(() => {
      console.log(8)
    })
})
.then(() => {
  console.log(6)
})


上面这道题是经常做错以及理解后,还是可能做错的错误率很高的题目,所以这里想详细的分析一波,其他题目对应的参考文章里面已经写得很清楚。这里是我个人最容易犯错的。关键一句话,分析出微任务堆栈!!!

1.一开始同步任务(也是第一个宏任务):输出 1

2.微任务队列变迁:

[红框]:清红框,执行到resolve(输出 2,3,中间遇到settimeout,放入宏任务),遇到微任务-蓝框,放入队列,然后红框算是清空了,对应的响应红框.then 的绿框放入队列。

[蓝框、绿框]:开始清蓝框,执行到resolve(输出4,5),遇到微任务-黄框,放入队列,然后蓝框算是清空了。然后对应的响应蓝框.then 的灰框放入队列.

[绿框、黄框、灰框]:开始清绿框(输出6), 开始清黄框(输出7),清完黄框遇到白框,放入队列。

[灰框、 白框]:开始清灰框(输出8),开始清白框(输出9),此时微任务全部清空了。最后清宏任务。

3.宏任务变迁: 清settimeout宏任务(输出10)

毕业题: 考察resolve放在宏任务里面,那么对应的微任务就将在宏任务里面才执行,以及finally的传参为undefined。

async function async1() {
  console.log('async1 start');
  new Promise((resolve, reject) => {
    try {
      throw new Error('error1')
    } catch (e) {
      console.log(e);
    }
    setTimeout(() => {
      resolve('promise4')
    }, 3 * 1000);
  })
    .then((res) => {
      console.log(res);
    }, err => {
      console.log(err);
    })
    .finally(res => {
      console.log(res);
    })
  console.log(await async2());
  console.log('async1 end'); 
}

function async2() {
  console.log('async2');
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(2)
    }, 1 * 3000);
  })
}

console.log('script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0)

async1();

new Promise((resolve) => {
  console.log('promise1');
  resolve();
})
  .then(() => {
    console.log('promise2');
    return new Promise((resolve) => {
      resolve()
    })
      .then(() => {
        console.log('then 1-1')
      })
  })
  .then(() => {
    console.log('promise3');
  })


console.log('script end');


参考文章:

【前端体系】从一道面试题谈谈对EventLoop的理解