林秀栋的技术博客

setTimeout,promise,async await的区别面试

// 今日头条面试题
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function () {
  console.log("settimeout");
});
async1();
new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("promise2");
});
console.log("script end");

题目的本质,就是考察 setTimeout、promise、async await 的实现及执行顺序,以及 JS 的事件循环的相关问题。

// 答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
// 题目2

const p = Promise.resolve();
(async () => {
    await p;
    console.log('await end');
})();
p.then(() => {
    console.log('then 1');
}).then(() => {
    console.log('then 2');
});

// 答案:

then 1
then 2
await end

event loop

JS 主线程不断的循环往复的从任务队列中读取任务,执行任务,其中运行机制称为事件循环(event loop)。

Microtasks、Macrotasks(task)

在高层次上,JavaScript 中有 microtasks 和 macrotasks(task),它们是异步任务的一种类型,Microtasks 的优先级要高于 macrotasks,microtasks 用于处理 I/O 和计时器等事件,每次执行一个。microtask 为 async/await 和 Promise 实现延迟执行,并在每个 task 结束时执行。在每一个事件循环之前,microtask 队列总是被清空(执行)。

下面是它们所包含的 api:

注意:

  1. 每一个 event loop 都有一个 microtask queue
  2. 每个 event loop 会有一个或多个 macrotask queue ( 也可以称为 task queue )
  3. 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中
  4. 每一次 event loop,会首先执行 microtask queue, 执行完成后,会提取 macrotask queue 的一个任务加入 microtask queue, 接着继续执行 microtask queue,依次执行下去直至所有任务执行结束。

异步运行机制

// 1. 开始执行
console.log(1); //     2. 打印 1
setTimeout(function () {
  // 6. 浏览器在 0ms 后,将该函数推入任务队列
  console.log(2); // 7. 打印 2
  Promise.resolve(1).then(function () {
    // 8. 将 resolve(1) 推入任务队列  9. 将 function函数推入任务队列
    console.log("ok"); // 10. 打印 ok
  });
}); // 3.调用 setTimeout 函数,并定义其完成后执行的回调函数
setTimeout(function () {
  // 11. 浏览器 0ms 后,将该函数推入任务队列
  console.log(3); // 12. 打印 3
}); // 4. 调用 setTimeout 函数,并定义其完成后执行的回调函数
// 5. 主线程执行栈清空,开始读取 任务队列 中的任务
// output: 1  2 ok 3

async await、Promise、setTimeout

setTimeout

console.log("script start"); //1. 打印 script start
setTimeout(function () {
  console.log("settimeout"); // 4. 打印 settimeout
}); // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log("script end"); //3. 打印 script start
// 输出顺序:script start->script end->settimeout

Promise

Promise 本身是同步的立即执行函数, 当在 executor 中执行 resolve 或者 reject 的时候, 此时是异步操作, 会先执行 then/catch 等,当主栈完成后,才会去调用 resolve/reject 中存放的方法执行,打印 p 的时候,是打印的返回结果,一个 Promise 实例。

console.log("script start");
let promise1 = new Promise(function (resolve) {
  console.log("promise1");
  resolve();
  console.log("promise1 end");
}).then(function () {
  console.log("promise2");
});
setTimeout(function () {
  console.log("settimeout");
});
console.log("script end");
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

当 JS 主线程执行到 Promise 对象时,

// 回到文章开头经典的例子:

const p = Promise.resolve(); // 1. p 的状态为 resolve;
(async () => {
  await p; // 2. 返回,并将 函数体后面的语句 console.log('await end') 放入下一个事件循环的 microtask queue 中
  console.log("await end"); // 6. 执行,打印 await end
})();
p.then(() => {
  // 3. p 的状态为 resolve,会把 p.then() 放入当前事件循环的 microtask queue中。
  console.log("then 1"); // 4. 执行,打印 then 1
}).then(() => {
  console.log("then 2"); // 5. 执行,打印 then 2,当前 microtask queue 结束,运行下一个 microtask queue
});
// 输出结果:then 1->then 1->await end

例如:

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

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

console.log("script end");
// 输出结果:script start->script end->promise1->promise2->setTimeout

async await

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}

console.log("script start");
async1();
console.log("script end");

// 输出顺序:script start->async1 start->async2->script end->async1 end

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

await 的含义为等待,也就是 async 函数需要等待 await 后的函数执行完成并且有了返回结果( Promise 对象)之后,才能继续执行下面的代码。await 通过返回一个 Promise 对象来实现同步的效果。