温习事件循环

JS 是单线程的,也就是同一个时刻只能做一件事情

宏任务和微任务

微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。

常见宏任务:

  • setTimeout()
  • setInterval()
  • setImmediate()

常见微任务:

  • promise.then()、promise.catch()
  • new MutaionObserver()
  • process.nextTick()
console.log('同步代码1');
setTimeout(() => {
    console.log('setTimeout')
}, 0)
new Promise((resolve) => {
  console.log('同步代码2')
  resolve()
}).then(() => {
    console.log('promise.then')
})
console.log('同步代码3');
// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"

上面的代码将按如下顺序输出为:”同步代码 1″、”同步代码 2″、”同步代码 3″、”promise.then”、”setTimeout”,具体分析如下。

(1)setTimeout 回调和 promise.then 都是异步执行的,将在所有同步代码之后执行;

顺便提一下,在浏览器中 setTimeout 的延时设置为 0 的话,会默认为 4ms,NodeJS 为 1ms。具体值可能不固定,但不是为 0。

(2)虽然 promise.then 写在后面,但是执行顺序却比 setTimeout 优先,因为它是微任务;

(3)new Promise 是同步执行的,promise.then 里面的回调才是异步的。

下面我们看一下上面代码的执行过程演示:

也有人这样去理解:微任务是在当前事件循环的尾部去执行;宏任务是在下一次事件循环的开始去执行。

例题1

试着自己回答一下这道题,求打印顺序:

console.log(1);

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

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

解答:

第1步,console.log(1);,打印1

位置任务
调用栈console.log(1);
微任务
宏任务

第2步,setTimeout,不打印

位置任务
调用栈setTimeout
微任务
宏任务setTimeout的回调

第3步,new Promise的函数参数,注意,Promise的函数参数是同步执行的,打印4

位置任务
调用栈new Promise的函数参数
微任务new Promise的then的回调
宏任务setTimeout 的回调

第4步,第二个setTimeout,不打印

位置任务
调用栈第二个setTimeout
微任务new Promise的then的回调
宏任务setTimeout 的回调,第二个setTimeout的回调

第5步,console.log(7);,打印7

位置任务
调用栈console.log(7);
微任务new Promise的then的回调
宏任务setTimeout 的回调,第二个setTimeout的回调

第6步,第一个微任务,即new Promise的then的回调,打印5

位置任务
调用栈new Promise的then的回调
微任务
宏任务setTimeout的回调,第二个setTimeout的回调

第7步,第一个宏任务,即setTimeout的回调,打印2,同时,Promise.resolve().then()的回调进入微队列

位置任务
调用栈setTimeout的回调
微任务Promise.resolve().then()的回调
宏任务第二个setTimeout的回调

第8步,第一个微任务,即Promise.resolve().then()的回调,打印3

位置任务
调用栈Promise.resolve().then()的回调
微任务
宏任务第二个setTimeout的回调

第9步,第一个宏任务,即第二个setTimeout的回调,打印6

位置任务
调用栈第二个setTimeout的回调
微任务
宏任务

是不是跟你演算的一样呢?

例题2

考察then方法链的执行顺序:

new Promise((resolve, reject) => {
  console.log(1)
  resolve(2)
}).then((data) => {
  // 1号回调
  console.log(data);
  return 3
}).then((data) => {
  // 2号回调
  console.log(data);
})

new Promise((resolve, reject) => {
  console.log(5)
  resolve(6)
}).then((data) => {
  // 3号回调
  console.log(data);
  return 7;
}).then((data) => {
  // 4号回调
  console.log(data);
})

你以为会打印1 2 3 5 6 7吗?错!你以为会打印1 5 2 3 6 7吗?也错!

  1. new Promise的函数参数是同步代码,所以先打印1。同时将第一个then的回调(1号回调)放入微队列。
  2. 同理,打印5,将第二个new Promise的第一个then的回调(3号回调)放入微队列。
  3. 执行1号回调,打印2,将2号回调放入微队列。
  4. 执行3号回调,打印6,将4号回调放入微队列。
  5. 执行2号回调,打印3。
  6. 执行4号回调,打印7。

所以结果是1 5 2 6 3 7,你演算对了吗?

例题3

有如下代码:

setTimeout(() => {
  console.log(1)
  setTimeout(() => {
    console.log(2)
  })
})

不许修改这段代码,只允许在外层作用域添加代码,如何实现在打印1跟打印2中间插入打印3

其实这个题跟例题2类似,考察的也是基本知识:

setTimeout(() => {
  console.log(1)
  setTimeout(() => {
    console.log(2)
  })
})

setTimeout(() => {
  console.log(3)
  setTimeout(() => {
    console.log(4)
  })
})

结果:打印 1 3 2 4。

原因:同例题2。

例题4

这次考察对async/await的执行顺序的理解:

console.log('script start');

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

async function async2() {
    console.log('async2 end');
};

async1()

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

new Promise((resolve, reject) => {
    console.log('promise start');
    resolve()
})
.then(() => console.log('promise end'))

console.log('script end')
  1. 打印script start
  2. 执行async1()async1的第一行是await async2(),这一句应拆开考察,其中async2()会同步执行,同时把await下方的所有代码放到微队列。
  3. 执行setTimeout,不打印。同时把回调放入宏队列。
  4. 执行new Promise的函数参数,打印promise start,同时把then的回调放入微队列 ,此时,微队列有2块代码,分别是await下面的代码(即console.log('async1 end');),以及console.log('promise end')
  5. console.log(‘script end’),打印script end
  6. 执行微队列第一个,也就是打印async1 end
  7. 执行微队列第二个,也就是打印promise end
  8. 执行宏任务,也就是打印setTimeout

注意:低版本的Chrome浏览器(大约是70版本之前)会先打印promise end,后打印async1 end,原因我忘却了,大致是Chrome的早期实现里有Bug,其实原因也不重要,既然高版本的浏览器纠正归来了,就行了。

例题5

证明await的赋值操作是异步任务:

let x = 5;
let y;
function ret() {
    x += 1;
    console.log('x是', x);
    console.log('y是', y);
    return x;
}
async function a() {
    y = await ret();
    console.log(y);
}
async function b() {
    y = await ret();
    console.log(y);
}

a()
b()

得到:

x是 6
y是 undefined
x是 7
y是 undefined
6
7

我们用反证法,假定await赋值是同步操作,那么a()y = await ret()会同步执行,之后才执行b()ret(),此时y有值,不应该是undefined,但事实是undefined,说明await赋值是异步操作。

例题6

证明new Promise()的函数参数和await后面的函数是同步任务,证明很简单:

var i;
for (i = 0; i < 20; i++) {
  new Promise(resolve => {
    console.log(i)
  })
}

也是反证法,假如new Promise()的函数参数是异步任务,那么应该像setTimeout一样打印20个20,然而事实上会打印等差数列。

注意,不要用for(let i = 0;)这种写法,因为这会形成一个特殊作用域,不能反证出我们的结论。

function log(x) { console.log(x) }
async function a(i) {
  await log(i)
}
var i;
for (i = 0; i < 20; i++) {
    a(i);
}

function log(x) { console.log(x) }
var i;
for (i = 0; i < 20; i++) {
    async function a() {
        await log(i)
    }
    a();
}

同理,因为上面2段代码的结果也都是等差数列,所以await后面的语句是同步任务。

例题7

证明await会暂停for循环:

var i;
function ret() {
    console.log('ret里的i', i);
    return 100;
}
async function a() {
    for (i = 0; i < 20; i++) {
        await ret();
        console.log(i);
    }
}
a();

得到等差数列。

虽然微队列放入了20个任务,但是由于await会暂停循环,也就是说i并不会像setTimeout时一样自顾自的增长为20,而是会等待每一个微任务执行完毕,由此证明await会暂停for循环。

相反,setTimeout不会阻止for循环:

var i;
function ret() {
    console.log('ret里的i', i);
    return 100;
}
async function a() {
    for (i = 0; i < 20; i++) {
        setTimeout(() => {
            ret();
            console.log(i);
        });
    }
}
a();

总结

  1. 随时遇到异步,随时放入队列,这是起码的准则。
  2. 先同步任务,同步任务有异步回调的时候,根据规则放入队列。
  3. 同步任务都完成了就执行微队列。有异步则继续放入队列。
  4. 微队列都完成了就执行宏队列第一个。有异步则继续放入队列。
  5. 观察微队列,有则执行,没则执行宏队列第二个。
  6. 重复3/4/5步骤。

欢迎阅读!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇