JS异步

前言 什么是异步?异步和同步又有什么区别?

通常来说,程序是按顺序执行的,同一时刻只会发生一件事。如果一个函数依赖另一个函数的结果,它那么它只能等待那个函数结束才能继续执行。


什么是同步?

一定要等任务执行完了,得到结果,才执行下一个任务。

示例

1
2
3
4
5
const btn = document.querySelector("button")
btn.addEventListener("click", () => {
alert("点击确定之后,后续代码才会执行")
console.log("aaa")
})

上述代码,一行行按顺序执行。

  1. 先获取DOM里的button引用
  2. 监听按钮的click事件
    alert弹框出现
    点击弹框确认按钮之后,打印”aaa”

每一个操作在执行的时候,其它任何事件都没有发生,因为JavaScript 任何时候只能做一件事情, 只有一个主线程,其他的事情都阻塞了,直到前面的操作完成。


异步

不等任务执行完,直接执行下一个任务。

示例

1
2
3
4
5
6
7
8
9
10
const asyncFunc = function (callback) {
return setTimeout(() => {
callback("异步任务执行结束后的结果")
}, 2000)
}

asyncFunc((result) => {
console.log(result)
})
console.log("aaa")

上述代码,先打印”aaa”,后延时2000毫秒执行callback回调。

什么情况下会用到异步?

当几个任务函数之间没有任何关系,相互之间没有任何影响时,就应该使用异步。

异步代码的本质

通过以下代码示例,来进一步了解异步代码的本质

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function() {
setTimeout(() => {
console.log(0);
});

new Promise(resolve => {

console.log(1);

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

Promise.resolve().then(() => console.log(5));

}).then(() => {

console.log(6);
Promise.resolve().then(() => console.log(7));
setTimeout(() => console.log(8));

});

console.log(9);
})();

简单版理解

  • js 的执行顺序,是先同步再异步。
  • 异步中任务的执行顺序: 先微任务 microtask队列,再宏任务macrotask队列
  • 调用Promise中的resolve,reject属于微任务队列,setTimeout属于宏任务队列

同步 > 异步;微任务 > 宏任务

对以上代码任务执行顺序进行编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function() {
setTimeout(() => {
console.log(0);
}); // 任务一

new Promise(resolve => {

console.log(1);
// 任务二
setTimeout(() => {
resolve(); // 任务三
Promise.resolve().then(() => { // 任务四
console.log(2);
setTimeout(() => console.log(3)); // 任务八
Promise.resolve().then(() => console.log(4)); // 任务九
});
});

Promise.resolve().then(() => console.log(5)); // 任务五

}).then(() => {

console.log(6);
Promise.resolve().then(() => console.log(7)); // 任务六
setTimeout(() => console.log(8)); // 任务七

});

console.log(9);
})();

第一步:
任务一的setTimeout 函数属于宏任务,先丢入宏列队。
同步执行,先打印1.同步执行打印 9

微任务 宏任务
任务五 任务一
任务二

第二步:
任务二丢入宏列队,执行微任务任务三,打印5
第三步:
同步任务及微任务已经执行完毕,开始执行宏任务

  • 执行任务一,打印0
  • 执行任务二
    1
    2
    3
    4
    5
    6
    7
    8
    setTimeout(() => {
    resolve(); // 任务三
    Promise.resolve().then(() => { // 任务四
    console.log(2);
    setTimeout(() => console.log(3)); // 任务八
    Promise.resolve().then(() => console.log(4)); // 任务九
    });
    });

第四步:
执行任务三,打印 6,将任务六丢入微任务中,任务七丢入宏任务中

第五步:
执行任务四,打印 2,将任务八丢入宏任务,任务九丢入微任务

第六步:
执行任务六,打印7
执行任务九,打印4
任务七,打印8
任务八,打印3

微任务 宏任务
任务三
任务四
任务六 任务七
任务九 任务八

最终打印输出的顺序为1,9,5,0,6,2,7,4,8,3

2020.0519补充

如何分析异步执行的顺序

  • 首先我们分析有多少个宏任务
  • 在每个宏任务中,分析有多少个微任务
  • 根据调用次序,确定宏任务中的微任务执行次序
  • 根据宏任务的触发规则和调用次序,确定宏任务的执行次序
  • 确定整个顺序。

新特性 async/await

async 函数是一种特殊语法,特征是在 function 关键字之前加上 async 关键字,这样,就定义了一个 async 函数,我们可以在其中使用 await 来等待一个 Promise。async 函数必定返回 Promise。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function sleep(duration) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
async function foo(name){
await sleep(2000)
console.log(name)
}
async function foo2(){
await foo("a");
await foo("b");
}

小结

异步还是同步执行代码,取决于我们要做什么。