JS异步
2024-01-24 20:18:52 #JS

单线程和异步

JS 是单线程语言,在同一时刻只能执行一个任务。

JS 代码分为同步代码和异步代码:

  • 同步代码是顺序执行的。JS 引擎只有一个主线程,按照编写的顺序依次执行代码
1
2
3
4
// 依次打印 123
console.log(1);
console.log(2);
console.log(3);
  • 异步代码是异步执行的。异步代码不会阻塞主线程,而是被添加到一个任务队列中,当主线程执行完同步代码后,会从任务队列中取出一个异步任务并执行
1
2
3
4
5
6
7
8
9
// setTimeout 是一个异步任务,先打印 123,再打印 456
console.log(1);

setTimeout(() => { // 箭头函数作为第一个参数,是一个回调函数
console.log(456);
}, 0); // 第二个参数 0 的含义是,在 0 毫秒之后,将代码插入任务队列,而不是在 0 毫秒之后执行

console.log(2);
console.log(3);

异步任务包含三种:

  • 定时器(setTimeoutsetInterval
  • 网络事件触发(onloadonerror 等)
  • 鼠标键盘事件触发(onclickonkeydown 等)

处理异步任务的方法:

  • 回调函数是 JS 中处理异步任务最常用的方法,当异步任务执行完毕后,会调用回调函数
  • Promise 是解决“回调地狱”问题的更优雅的方法,使代码更加简洁易读
  • Generator 是一种解决异步编程的特殊函数
  • async 函数使得异步操作更加方便,是 Generator 函数的语法糖

异步的应用场景

  • 网络请求,如 AJAX、图片加载
1
2
3
4
5
6
// ajax
console.log('start');
$.get('./data1.json', function (data1) {
console.log(data1);
})
console.log('end');
1
2
3
4
5
6
7
8
// 图片加载
console.log('start');
let img = document.createElement('img');
img.onload = function () {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
  • 定时任务,如 setTimeout、setInterval
1
2
3
4
5
6
// setTimeout
console.log(100);
setTimeout(function () {
console.log(200);
}, 1000)
console.log(300);
1
2
3
4
5
6
// setInterval
console.log(100);
setInterval(function () {
console.log(200);
}, 1000)
console.log(300);

Event Loop

异步是基于回调实现的,Event Loop 又称事件循环或事件轮询,是异步回调的实现原理。

Event Loop 的执行过程

  • 同步代码,一行一行依次放入 Call Stack(调用栈)中执行
  • 遇到异步,先“记录”,等待时机(定时、网络请求等)
  • 时机到了,就移动到 Callback Queue(任务队列)
  • Call Stack 为空(即同步代码执行完毕),则先尝试 DOM 渲染,再触发 Event Loop
  • 轮询查找 Callback Queue,如有,则移动到 Call Stack 执行
  • 继续轮询查找,如此反复
1
2
3
4
5
6
7
8
// 简单示例
console.log('Hi'); // 同步代码

setTimeout(function callback1() { // 异步代码
console.log('cb1');
}, 5000);

console.log('Bye'); // 同步代码

上述示例的运行过程为:

  • 将同步代码 console.log('Hi'); 推入 Call Stack 中进行执行,并在 Browser Console 中显示打印结果 'Hi',执行完毕则在 Call Stack 中移除
  • 将异步的定时器函数 setTimeout 放入 Web APIs 中,并开始计时
  • 将同步代码 console.log('Bye'); 推入 Call Stack 中进行执行,并在 Browser Console 中显示打印结果 'Bye',执行完毕则在 Call Stack 中移除
  • 此时同步代码全部执行完毕,Call Stack 空置
  • 5000ms 后,将异步函数移入 Callback Queue 中,即刻启动 Event Loop
  • 将异步函数推入 Call Stack 中执行函数体,在 Browser Console 中显示打印结果 'cb1',执行完毕将其移出 Call Stack

DOM 事件与 Event Loop 的关系

  • 异步(setTimeout、AJAX 等)使用回调,基于 Event Loop
  • DOM 事件也使用回调,基于 Event Loop

Promise

Promise 的基本使用

callback hell 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取第一份数据
$.get(url1, (data1) => {
console.log(data1);

// 获取第二份数据
$.get(url2, (data2) => {
console.log(data2);

// 获取第二份数据
$.get(url3, (data3) => {
console.log(data3);

// 获取更多的数据
})
})
})

promise 解决 callback hell 问题:

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
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data);
},
error(err) {
reject(err);
}
})
})
}

const url1 = '/data1.json';
const url2 = '/data2.json';
const url3 = '/data3.json';

getData(url1).then(data1 => {
console.log(data1);
return getData(url2);
}).then(data2 => {
console.log(data2);
return getData(url3);
}).then(data3 => {
console.log(data3);
}).catch(err => {
console.error(err);
})

使用 promise 加载图片示例:

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
31
32
function loadImg(src) {
// pending
return new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
resolve(img); // fulfilled
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err); // rejected
}
img.src = src;
})
}

const url1 = 'https://albertbobo.github.io/images/avatar.png';
const url2 = 'https://img2.sycdn.imooc.com/545847e20001163c02200220-140-140.jpg';

loadImg(url1).then(img1 => {
document.body.appendChild(img1);
console.log('图片1已加载');
console.log('图片1的宽度:', img1.width);
return img1; // 返回普通对象
}).then(img1 => {
console.log('图片1的高度:', img1.height);
return loadImg(url2); // 返回 promise 实例
}).then(img2 => {
document.body.appendChild(img2);
console.log('图片2已加载');
}).catch(ex => {
console.error(ex);
})

Promise 的三种状态

一个 Promise 必然存在以下三种状态之一:

  • pending(待定):初始状态,既没有被兑现,也没有被拒绝
  • fulfilled(已兑现):意味着操作成功完成
  • rejected(已拒绝):意味着操作失败

两种方法将 Promise 的状态由 pending 转化为 fulfilled 或者 rejected,且变化是不可逆的:

  • pending -> resolve() 方法 -> fulfilled
  • pending -> reject() 方法 -> rejected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const p1 = new Promise((resolve, reject) => {
})
console.log('p1: ', p1); // pending

const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
})
})
console.log('p2: ', p2); // pending(刚开始打印时)
setTimeout(() => console.log('p2-setTimeout: ', p2)); // fulfilled

const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject();
})
})
console.log('p3: ', p3); // pending(刚开始打印时)
setTimeout(() => console.log('p3-setTimeout: ', p3)); // rejected

Promise 的方法

Promise.prototype.then()

Promise 并不直接返回最后的结果,而是将它们放在 resolve() 方法或 reject() 方法里面,使用 then() 方法获取异步回调的值。

  • then() 方法是定义在原型对象 Promise.prototype 上的
  • then() 方法的第一个参数是 fulfilled 状态的回调函数,第二个参数是 rejected 状态的回调函数,它们都是可选的

注意!then() 方法里面的两个参数虽然都是可选的,但是如果 Promise 返回的是 rejected 状态,而且需要调用它的值,就必须设置两个参数,在第二个参数上调用 rejected 的值。如果是 fulfilled 状态,第二个参数可以省略。

  • then() 方法返回的是一个新的 Promise 实例,因此可以使用链式写法,即 then() 方法后面再调用另一个 then() 方法,这是解决回调地狱的关键
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
new Promise((resolve, reject) => {
resolve();
}).then(() => {
console.log('ok 1');
}).then(() => {
console.log('ok 2');
})

// 输出结果:
// ok 1
// ok 2


// 等价于以下写法:

// p1 状态为 fulfilled,将触发 p1.then 方法的第一个参数
let p1 = new Promise((resolve, reject) => {
resolve();
})

// then 方法的第一个回调函数被执行后,p2 状态为 fulfilled,将触发 p2.then 的第一个参数
let p2 = p1.then(() => {
console.log('ok 1');
})

// p3 状态为 fulfilled
let p3 = p2.then(() => {
console.log('ok 2');
})
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
31
32
33
new Promise((resolve, reject) => {
reject('error');
}).then(() => {
console.log('ok 1');
}, err => {
console.error('error 1: ' + err);
}).then(() => {
console.log('ok 2');
})

// 输出结果:
// error 1: error
// ok 2


// 等价于以下写法:

// p1 状态为 rejected,将触发 p1.then 方法的第二个参数
let p1 = new Promise((resolve, reject) => {
reject('error');
})

// p1.then 的第一个参数不会被触发,只触发第二个参数
let p2 = p1.then(() => {
console.log('ok 1');
}, function fn2(err) { // fn2 执行后,p2 的状态为 fulfilled,将触发 p2.then 的第一个参数
console.error('error 1: ' + err);
})

// p3 状态为 fulfilled
let p3 = p2.then(() => {
console.log('ok 2');
})

Promise.prototype.catch()

catch() 方法是 then(null, rejection)then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。

  • 如果异步操作抛出错误,状态就会变为 rejected,就会调用 catch() 方法指定的回调函数来处理这个错误
  • Promise 对象应该调用 catch()方法,以便处理 Promise 内部发生的错误
  • catch() 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Promise((resolve, reject) => {
resolve(); // fulfilled
}).then(() => {
throw new Error('then error 1'); // rejected
}).catch(err => { // catch 方法被触发回调
console.error('catch error 1: ' + err); // fulfilled
}).then(() => {
console.log('ok 1');
}, err => {
console.log('error 1');
})

// 输出结果:
// catch error 1: Error: then error 1
// ok 1
1
2
3
4
5
6
7
8
9
10
11
12
new Promise((resolve, reject) => {
reject('error'); // rejected
}).then(() => {
console.log('ok 1');
}).then(() => {
console.log('ok 2');
}).catch(err => {
console.error('catch error 1: ' + err); // fulfilled
})

// 输出结果:
// catch error 1: error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Promise((resolve, reject) => {
reject('error'); // rejected
}).then(() => {
console.log('ok 1');
}).then(() => {
console.log('ok 2');
}, err => {
console.error('error 1: ' + err);
throw 'error 2'; // rejected
}).catch(err => {
console.error('catch error: ' + err); // fulfilled
})

// 输出结果:
// error 1: error
// catch error: error 2

总结:

  • 错误在传递的过程中如果被中途“处理”(触发 then 的第二个参数,或者被 catch),则不再继续传递;否则继续传递,直到被处理
  • 错误被“处理”之后,过程中的 Promise 对象全部为 fulfilled 状态

then(), catch() 小结

Promise 状态的表现:

  • pending 状态,不会触发 then() 和 catch() 方法
  • fulfilled 状态,会触发 then() 方法的第一个回调函数
  • rejected 状态,会触发 then() 方法的第二个回调函数,或者触发 catch() 方法的回调函数
1
2
3
4
5
6
7
8
9
const p1 = Promise.resolve(100);        // fulfilled 
p1.then(data => {
console.log('data1: ', data);
}).catch(err => {
console.error('err1: ' + err);
})

// 输出结果:
// data1: 100
1
2
3
4
5
6
7
8
9
const p2 = Promise.reject('err');       // rejected
p2.then(data => {
console.log('data2: ', data);
}).catch(err => {
console.error('catch err2: ' + err);
})

// 输出结果:
// catch err2: err
1
2
3
4
5
6
7
8
9
10
11
const p3 = Promise.reject('err');       // rejected
p3.then(data => { // then 的第一个回调函数不会被触发
console.log('data3: ', data);
}, err => { // then 的第二个回调函数会被触发
console.error('err3: ' + err); // 执行后状态变为 fulfilled
}).catch(err => { // catch 不会被触发
console.error('catch err3: ' + err);
})

// 输出结果
// err3: err

then 和 catch 方法对状态的改变:

  • then() 方法在正常情况下返回 fulfilled,如果里面有错误则返回 rejected
1
2
3
4
5
6
7
8
9
10
11
12
const p1 = Promise.resolve().then(() => {
return 100;
});

console.log(p1); // p1 的状态为 fulfilled

p1.then(() => {
console.log('ok 1');
})

// 输出结果:
// ok 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p2 = Promise.resolve().then(() => {
throw new Error('then error');
});

console.log(p2); // p2 的状态为 rejected

p2.then(() => {
console.log('ok 2');
}).catch(err => {
console.error('catch error 2: ' + err);
})

// 输出结果:
// catch error 2: Error: then error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p3 = Promise.resolve().then(() => {
const x = 'a';
x = 'b';
});

console.log(p3); // p3 的状态为 rejected

p3.then(() => {
console.log('ok 3');
}).catch(err => {
console.error('catch error 3: ' + err);
})

// 输出结果:
// catch error 3: TypeError: Assignment to constant variable.
  • catch() 方法在正常情况下返回 fulfilled,如果里面有错误则返回 rejected
1
2
3
4
5
6
7
8
9
10
11
12
const p1 = Promise.reject('my error').catch(err => {
console.error(err);
})

console.log(p1); // p1 状态为 fulfilled

p1.then(() => {
console.log('ok 1');
})

// 输出结果:
// ok 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p2 = Promise.reject('my error').catch(err => {
throw new Error('catch err: ' + err);
})

console.log(p2); // p2 的状态为 rejected

p2.then(() => {
console.log('ok 2');
}).catch(err => {
console.error('catch error 2: ' + err);
})

// 输出结果:
// catch error 2: Error: catch err: my error

Promise.prototype.finally()

finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

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
31
32
33
34
35
new Promise((resolve, reject) => {
reject('error');
}).then(() => {
console.log('ok 1');
}, err => {
console.log('error 1:' + err);
}).then(() => {
console.log('ok 2');
}, err => {
console.log('error 2:' + err);
}).then(() => {
console.log('ok 3');
}, err => {
console.log('error 3:' + err);
}).catch(err => {
console.log('catch 1:' + err);
}).finally(() => {
console.log('finally 1');
}).then(() => {
console.log('ok 4');
}, err => {
console.log('error 4:' + err);
}).finally(() => {
console.log('finally 2');
}).catch(err => {
console.log('catch 2:' + err);
})

// 输出结果:
// error 1: error
// ok 2
// ok 3
// finally 1
// ok 4
// finally 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const promise = new Promise((resolve, reject) => {
resolve('ok!');
setTimeout(() => {
reject('oops!');
}, 500);
})

promise.then(val => {
console.log('success: ' + val);
}).catch(err => {
console.error('error: ' + err);
}).finally(() => {
console.log('finally');
})

// 输出结果:
// success: ok!
// finally

Promise.all()

all() 方法用于将多个 Promise 实例包装成一个新的 Promise 实例。其参数为一个可迭代的对象(例如:Array, Map, Set),返回值是一个 Promise 实例。

let p = Promise.all([p1, p2, p3]);

p 的状态由 p1、p2、p3 决定,分为两种情况:

  • 只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数
  • 只要 p1、p2、p3 中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 rejected 的实例的返回值会传递给 p 的回调函数
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
31
32
33
const p1 = new Promise((resolve, reject) => {
resolve('ok 1');
});

const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok 2');
}, 1000)
});

const p3 = new Promise((resolve, reject) => {
reject('oops 1');
});

const p4 = new Promise((resolve, reject) => {
reject('oops 2');
});

Promise.all([p1, p2]).then(result => {
console.log(result);
}).catch(err => {
console.error(err);
})
// 1000ms后输出结果:
// ['ok 1', 'ok 2']

Promise.all([p1, p2, p3, p4]).then(result => {
console.log(result);
}).catch(err => {
console.error('error: ' + err);
})
// 立即输出结果:
// error: oops 1

注意!如果作为参数的 Promise 实例被 rejected,但是自身定义了 catch() 方法且没有错误,则此时状态变为 fulfilled,那么并不会触发 Promise.all() 的 catch() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const p1 = new Promise((resolve, reject) => {
resolve('ok 1');
}).then(val => val).catch(err => err);

const p2 = new Promise((resolve, reject) => {
throw new Error('oops 1');
}).then(val => val).catch(err => err);

Promise.all([p1, p2]).then(result => {
console.log(result);
}).catch(err => {
console.error(err);
})

// 输出结果:
// ['ok 1', Error: oops 1]

Promise.race()

race() 方法同样将一个 Promise 可迭代对象包装成一个新的 Promise 实例。

let p = Promise.race([p1, p2, p3]);

只要 p1、p2、p3 中有一个实例率先确定状态,p 的状态就随之确定,且该实例的返回值就是传递给 p 的回调函数。

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
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok 1');
}, 1000)
});

const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok 2');
}, 500)
});

const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('oops 1');
}, 500)
});

Promise.race([p1, p2, p3]).then(result => {
console.log(result);
}).catch(err => {
console.error('error: ' + err);
})

// 输出结果:
// ok 2

async/await

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。在函数执行时,一旦遇到 await 就会先返回,等待异步操作完成,再接着执行函数体内后面的语法。

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
31
32
// 使用 async/await 改写加载图片示例
function loadImg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
resolve(img);
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`);
reject(err);
}
img.src = src;
})
}

const url1 = 'https://albertbobo.github.io/images/avatar.png';
const url2 = 'https://img2.sycdn.imooc.com/545847e20001163c02200220-140-140.jpg';

async function loadImg2() {
const img2 = await loadImg(url2);
return img2;
}

(async function () {
// img1
const img1 = await loadImg(url1); // await 后面可以是一个 Promise 对象
console.log(img1.height, img1.width);

// img2
const img2 = await loadImg2(); // await 后面也可以是一个 async 函数
console.log(img2.height, img2.width);
})()

async/await 和 Promise 的关系

返回 Promise 对象

  • 执行 async 函数返回的是一个 Promise 对象
  • async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数
1
2
3
4
5
6
7
8
9
10
11
async function fn1() {
return 100; // 等同于 return Promise.resolve(100)
}

const res1 = fn1();

console.log('res1: ', res1); // Promise 对象,状态为 fulfilled

res1.then(data => {
console.log(data); // 100
});
1
2
3
4
5
6
7
8
9
10
11
async function fn2() {
return Promise.resolve(200);
}

const res2 = fn2();

console.log('res2: ', res2);

res2.then(data => {
console.log(data); // 200
});
  • async 函数内部抛出错误会导致返回的 Promise 对象变为 rejected 状态,抛出的错误对象会被 catch 方法回调函数接收到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function fn3() {
throw new Error('oops!');
}

const res3 = fn3();

console.log('res3: ', res3); // Promise 对象,状态为 rejected

res3.then(data => {
console.log(data);
}, err => {
console.error(err);
});

// 输出结果:
// Error: oops!

await 命令

  • await 相当于 Promise 的 then
1
2
3
4
5
6
7
8
9
10
11
(async function () {
const p = Promise.resolve(300);
const data = await p;
console.log(data); // 300
})();

// 等同于
(async function () {
const p = Promise.resolve(300);
p.then(data => console.log(data));
})();
1
2
3
4
(async function () {
const data = await fn2();
console.log(data); // 200
})();
1
2
3
4
5
6
7
8
9
10
async function fn() {
return 300;
}

(async function () {
const a = fn();
const b = await fn();
console.log(a); // Promise(fulfilled)
console.log(b); // 300
})();
  • 正常情况下,await 命令后面是一个 Promise 对象,如果不是,会被转成一个立即 resolve 的对象
1
2
3
4
(async function () {
const data = await 400; // 等同于 await Promise.resolve(400)
console.log(data); // 400
})();
  • await 命令后面的 Promise 对象如果为 rejected 状态,则 reject 的参数会被 catch 方法的回调函数接收到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(async function () {
console.log('start');
const a = await 100;
console.log('a: ', a);
const b = await Promise.resolve(200);
console.log('b: ', b);
const c = await Promise.reject(300);
console.log('c: ', c);
console.log('end');
})();

// 输出结果:
// start
// a: 100
// b: 200
// 报错:Uncaught (in promise)
1
2
3
4
5
6
7
8
9
10
(async function fn4() {
await Promise.reject('oops!');
})().then(data => {
console.log(data);
}).catch(err => {
console.error(err);
});

// 输出结果:
// oops!

错误处理

  • 如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function fn5() {
await new Promise((resolve, reject) => {
throw new Error('oops!');
})
}

fn5().then(data => {
console.log(data);
}).catch(err => {
console.error(err);
});

// 输出结果:
// Error: oops!
  • 捕获异常的方法是将其放在 try...catch代码块中,相当于 Promise 的 catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function fn5() {
try {
await new Promise((resolve, reject) => {
throw new Error('oops!');
});
} catch (err) {
console.error(err);
}
}

fn5();

// 输出结果:
// Error: oops!
1
2
3
4
5
6
7
8
9
10
11
12
(async function () {
const p = Promise.reject('oops!');
try {
const res = await p;
console.log(res);
} catch (err) {
console.error(err);
}
})();

// 输出结果:
// oops!
  • 如果有多个 await 命令,则可以统一放在 try...catch 结构中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(async function () {
try {
const val1 = await Promise.resolve(100);
const val2 = await Promise.resolve(200);
const val3 = await Promise.resolve(300);
const res = val1 + val2 + val3;
console.log('Final: ', res);
} catch (err) {
console.error(err);
}
})();

// 输出结果:
// Final: 600

async/await 是语法糖

  • async/await 是 Promise 的语法糖
  • async/await 是消灭异步回调的终极武器,但是异步的本质仍然是回调函数
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
31
32
33
34
35
36
37
38
39
40
async function async1() {
console.log('async 1 start');
await async2();
// await 的下一行开始,都可以看作是异步回调的内容
console.log('ok 1');
await async3();
console.log('ok 2');
console.log('async 1 end');

// 相当于
// async2().then(() => {
// console.log('ok 1');
// async3().then(() => {
// console.log('ok 2');
// console.log('async 1 end');
// })
// })
}

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

async function async3() {
console.log('async 3');
}

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

// 输出结果:
// script start
// async 1 start
// async 2
// script end
// ok 1
// async 3
// ok 2
// async 1 end
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
async function async1() {
console.log('async 1 start');
await async2();
console.log('async 1 end');
}

async function async2() {
console.log('async 2 start');
await async3();
console.log('async 2 end');
}

async function async3() {
console.log('async 3 start');
console.log('async 3 end');
}

async1();

// 输出结果:
// async 1 start
// async 2 start
// async 3 start
// async 3 end
// async 2 end
// async 1 end

宏任务与微任务

  • 宏任务:setTimeout,setInterval,Ajax,DOM 事件
  • 微任务:Promise,async/await

注意!微任务的执行时机比宏任务更早

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log('1');

// 宏任务
setTimeout(() => {
console.log('2');
});

// 微任务
Promise.resolve().then(() => {
console.log('3');
});

console.log('4');

// 输出结果:1432
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log('1');

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

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

console.log('7');

// 输出结果:1347562
// 注意:只有 then、catch 回调才是微任务

Event Loop 和 DOM 渲染

  • 每当 Call Stack 清空时,都是 DOM 重新渲染的机会
  • DOM 结构如有改变,则先进行 DOM 渲染,再接着触发下一次 Event Loop

注意!DOM 渲染由 GUI 线程处理,JS 执行由 JS 引擎线程处理,两个线程互斥,不可以同步执行

宏任务和微任务的区别

  • 微任务在 DOM 渲染前触发,宏任务在 DOM 渲染后触发
  • 因此,微任务的执行时机比宏任务更早
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const container = document.getElementById('container');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
const p3 = document.createElement('p');
p1.innerHTML = '一段文字 1';
p2.innerHTML = '一段文字 2';
p3.innerHTML = '一段文字 3';
container.append(p1);
container.append(p2);
container.append(p3);

// 宏任务
setTimeout(() => {
console.log(container.children.length);
alert('宏任务 setTimeout 在 DOM 渲染后触发');
})

// 微任务
Promise.resolve().then(() => {
console.log(container.children.length);
alert('微任务 Promise 在 DOM 渲染前触发');
})
  • 微任务是 ES6 规定的,宏任务是浏览器规定的
  • 在 Event Loop 中,微任务放入 Micro Task Queue 中(而不是 Callback Queue),不会经过 Web APIs
  • 执行顺序:同步任务 > 微任务 > DOM 渲染 > 宏任务
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
31
32
33
34
35
36
37
38
39
async function async1() {
console.log('async 1 start'); // 同步任务 2
await async2();
console.log('async 1 end'); // 微任务 1
}

async function async2() {
console.log('async 2'); // 同步任务 3
}

console.log('script start'); // 同步任务 1

setTimeout(function () {
console.log('setTimeout'); // 宏任务 1
}, 0)

async1();

// 初始化 Promise 时,传入的函数会被立即执行
new Promise(function (resolve) {
console.log('promise 1 start'); // 同步任务 4
resolve();
console.log("promise 1 end"); // 同步任务 5
}).then(function () {
console.log('promise 2'); // 微任务 2
})

console.log('script end'); // 同步任务 6

// 输出结果:
// script start
// async 1 start
// async 2
// promise 1 start
// promise 1 end
// script end
// async 1 end
// promise 2
// setTimeout

手写 Promise

Promise 构造函数与链式调用

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class MyPromise {
state = 'pending'; // 状态
value = undefined; // 成功后的值
reason = undefined; // 失败后的值

resolveCallbacks = []; // pending 状态下,存储成功的回调
rejectCallbacks = []; // pending 状态下,存储失败的回调

constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.resolveCallbacks.forEach(fn => fn(this.value));
}
}

const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.rejectCallbacks.forEach(fn => fn(this.reason));
}
}

try {
fn(resolveHandler, rejectHandler);
} catch (err) {
rejectHandler(err);
}
}

then(fn1, fn2) {
// pending 状态下,fn1、fn2 会被存储到 callbacks 中
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v;
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e;

if (this.state === 'pending') {
return new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (err) {
reject(err);
}
});

this.rejectCallbacks.push(() => {
try {
const newReason = fn2(this.reason);
reject(newReason);
} catch (err) {
reject(err);
}
});

})
}

if (this.state === 'fulfilled') {
return new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (err) {
reject(err);
}
})
}

if (this.state === 'rejected') {
return new MyPromise((resolve, reject) => {
try {
const newReason = fn2(this.reason);
reject(newReason);
} catch (err) {
reject(err);
}
})
}
}

catch(fn) {
return this.then(null, fn);
}
}
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
const p1 = new MyPromise((resolve, reject) => {
// resolve(100);

reject('错误信息……');

// setTimeout(() => {
// resolve(100);
// }, 500);
})

console.log('p1: ', p1);

const p11 = p1.then(data1 => {
console.log('data1: ', data1);
return data1 + 1;
})

const p12 = p11.then(data2 => {
console.log('data2: ', data2);
return data2 + 2;
})

const p13 = p12.catch(err => {
console.error(err);
})

Promise 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new MyPromise((resolve, reject) => {
// resolve(100);

// reject('错误信息……');

setTimeout(() => {
resolve(100);
}, 500);
})
const p2 = MyPromise.resolve(200);
const p3 = MyPromise.resolve(300);
const p4 = MyPromise.reject('错误信息');

const p5 = MyPromise.all([p1, p2, p3]);
p5.then(result => console.log('all result: ', result));

const p6 = MyPromise.race([p1, p2, p3]);
p6.then(result => console.log('race result: ', result));

Promise.resolve()

1
2
3
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => resolve(value));
}

Promise.reject()

1
2
3
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason));
}

Promise.all()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MyPromise.all = function (promiseList = []) {
return new MyPromise((resolve, reject) => {
const result = []; // 存储 promiseList 所有的结果
const length = promiseList.length;
let resolvedCount = 0;

promiseList.forEach(p => {
p.then(data => {
result.push(data);
resolvedCount++;

// 已经遍历到了最后一个 promise
if (resolvedCount === length) {
resolve(result);
}
}).catch(err => {
reject(err);
})
})
})
}

Promise.race()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyPromise.race = function (promiseList = []) {
let resolved = false;
return new MyPromise((resolve, reject) => {
promiseList.forEach(p => {
p.then(data => {
if (!resolved) {
resolve(data);
resolved = true;
}
}).catch(err => {
reject(err);
})
})
})
}