异步编程详解
同步任务、微任务、异步任务
const first = () =>
new Promise((resolve, reject) => {
console.log(3); // 同步1 3
let p = new Promise((resolve, reject) => {
console.log(7); // 同步2 7
setTimeout(() => {
console.log(5); // 异步1 5
resolve(6);
console.log(p); // 异步2 fulfilled 1
}, 0);
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg); // 微任务1 1
});
});
first().then((arg) => {
console.log(arg); // 微任务2 2
});
console.log(4); // 同步3 4
在这段代码中,执行顺序如下:
console.log(3)
输出 3console.log(7)
输出 7- 设置一个
setTimeout
,将console.log(5)
和resolve(6)
放入异步任务队列 resolve(1)
立即执行,随后resolve(2)
也立即执行console.log(4)
输出 4- 微任务队列中的
p.then
先执行,输出 1 - 最后,
first().then
执行,输出 2 - 异步任务中的
console.log(5)
输出 5,随后console.log(p)
输出fulfilled
微任务
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1'); // 同步1 promise1
resolve();
})
.then(() => {
console.log('promise2'); // 微1 promise2
})
.then(() => {
console.log('promise3'); // 微2 promise3
})
.then(() => {
console.log('promise4'); // 微3 promise4
});
a = new Promise(async (resolve, reject) => {
console.log(a); // 同步2 undefined
console.log(b); // Promise fulfilled
await b; // 等待b执行结束后,代码继续往下执行
console.log(a); // 在等待b的时候 new Promise已经完成,promise pending
console.log('after1'); // after1
await a; // 等待a的处理结果,a 的结果在await之后,所以会一直等待
resolve(true);
console.log('after2');
});
console.log('end'); // 同步3 end
此代码展示了微任务的执行顺序:
- 输出
promise1
- 输出
undefined
和Promise fulfilled
- 输出
end
- 微任务队列依次执行,输出
promise2
、promise3
、promise4
await b
完成后,输出after1
await a
因为a
处于 pending 状态,后续代码暂停执行
Promise.all
function runAsync(x) {
const p = new Promise((resolve) => {
setTimeout(() => {
resolve(x, console.log(x));
}, 1000);
});
return p;
}
function runReject(x) {
const p = new Promise((res, rej) => {
setTimeout(() => {
rej(`Error:${x}`, console.log(x));
}, 1000 * x);
});
return p;
}
// 所有的Promise都成功了,res才会打印成功的值
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
// 13 一起出现,因为时间一样,先2后4,2的错误先到,打印错误2
.then((res) => console.log(res))
.catch((err) => console.log(err));
Promise.all
会等待所有的 Promise 完成。如果有任意一个 Promise 失败,Promise.all
会立即进入 catch
,输出第一个失败的错误。在此例中,runReject(2)
会在 2000ms 后先于 runReject(4)
报错,输出 Error:2
。
Promise.race
function runAsync(x) {
const p = new Promise((r) => {
setTimeout(() => r(x, console.log(x)), 1000);
});
return p;
}
// 123一起执行,启动三个定时器,123都会打印,1最先返回成功
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
// res的结果,接收返回最快的值
.then((res) => console.log('result:', res))
.catch((err) => console.log(err));
Promise.race
会在第一个 Promise 完成或失败时立即返回结果。在此例中,runAsync(1)
最先完成,输出 1
并且 then
中的回调输出 result: 1
。
async
async function async1() {
// async没有await,剩下的代码都是同步代码
console.log('1'); // 同步1
// promise 的参数是个函数,这个函数是同步执行的
new Promise((resolve) => {
// 直接执行
resolve();
console.log('2'); // 同步 2
});
console.log('3'); // 同步3
}
async1();
console.log('4'); // 同步4
在没有 await
的情况下,async
函数中的代码同步执行,输出顺序为 1
、2
、3
、4
。
await
async function async1() {
console.log('1'); // 同步1
await async2(); // 等待 async2 执行完,包括里面的异步
console.log('2'); // 微任务1
}
async function async2() {
setTimeout(() => {
console.log('3'); // 异步 1
}, 0);
console.log('4'); // 同步2
}
async1();
console.log(5); // 同步4
await
会暂停 async
函数的执行,等待 Promise 完成后继续。输出顺序为 1
、4
、5
、2
、3
。
异步任务
async function async1() {
console.log(1); // 同步1
await async2(); // 等待async2()
/* 微任务区域 */
console.log(2); // async2了就执行
setTimeout(() => {
console.log(3); // 异步3
}, 0);
/* 微任务区域 */
}
async function async2() {
// 最先注册的计时器
setTimeout(() => {
console.log(4); // 异步1
}, 0);
console.log(5); // 同步2
}
async1(); // 从这里开始执行
// async1();卡住了就继续执行当前宏任务
setTimeout(() => {
console.log(6); // 异步2
}, 0);
console.log(7); // 同步3
执行顺序:
- 输出
1
- 输出
5
- 输出
7
- 微任务输出
2
- 异步任务按注册顺序输出
4
、3
、6
pending
async function async1() {
console.log('1'); // 同步 2
/* 这里的 Promise 是 pending 状态所以下面的代码不回执行 */
await new Promise((resolve) => {
console.log('2'); // 同步3 因为是在括号里的
});
/* ↓是微任务 */
console.log('3');
return '4';
}
console.log('5'); // 同步1
async1().then((res) => console.log(res));
console.log('6'); // 同步 4
由于 new Promise
中没有调用 resolve
,Promise 始终处于 pending 状态,导致 await
后的代码不会执行。输出顺序为 5
、1
、2
、6
。
await 与微任务
async function async1() {
console.log('1'); // 同步2
await new Promise((resolve) => {
console.log('2'); // 同步3
resolve('3');
// 继续按照代码顺序执行
}).then((res) => console.log(res)); // 微1
console.log('4'); // 微2
return '5';
}
console.log('6'); // 同步1
async1().then((res) => console.log(res)); // 微3
console.log('7'); // 同步4
执行顺序:
- 输出
6
- 输出
1
- 输出
2
- 输出
7
- 微任务输出
3
- 微任务输出
4
- 微任务输出
5
微任务执行
async function async1() {
console.log(1); // 同步2
await new Promise((resolve) => {
console.log(2); // 同步3
resolve(3);
});
// 靠前面的微任务先执行
console.log(4); // 微1
return 5;
}
console.log(6); // 同步1
// 接收resolve(3)
async1().then((res) => {
console.log(res); // 微2
});
new Promise((resolve) => {
console.log(7); // 同步 4
setTimeout(() => {
console.log(8); // 异步1
}, 0);
});
输出顺序:
- 输出
6
- 输出
1
- 输出
2
- 输出
7
- 微任务输出
4
- 微任务输出
5
- 异步任务输出
8
多个微任务
async function testSomething() {
console.log('1'); // 同步 2
return '2'; // 返回的是包装后的promise 属于微任务
}
async function testAsync() {
console.log('3'); // 微任务 的同步任务1
return Promise.resolve('4');
}
async function test() {
console.log('5'); // 同步1
// v1第一个先注册的微任务回调
const v1 = await testSomething();
console.log(v1); // 微1
// v2 要等待 v1 的结果,v2 任务在v1后执行
const v2 = await testAsync();
console.log(v2); // 微3
console.log(v1, v2); // 微4
}
test();
let promise = new Promise((resolve) => {
console.log('6'); // 同步 3
resolve('7'); // 微任务 2
});
// 第二个注册的微任务
promise.then((val) => console.log(val)); // 微2 结果
console.log('8'); // 同步 4
执行顺序:
- 输出
5
- 输出
1
- 输出
6
- 输出
3
- 输出
8
- 微任务输出
2
- 微任务输出
7
- 微任务输出
4
reject
async function async1() {
await async2(); // 接收到reject,不继续执行
console.log('1');
return '2';
}
async function async2() {
return new Promise((resolve, reject) => {
console.log('3'); // 同步1
reject('4');
});
}
// 成功之后才会执行,所以会报错
async1()
.then((res) => console.log(res))
.catch((err) => {
console.log(err); // 捕获错误4
});
在 async2
中调用 reject
,导致 async1
中的 await
捕获到错误,进入 catch
,输出 4
。
try...catch
async function async1() {
try {
await Promise.reject('1');
} catch (e) {
console.log(2); // 1 捕获错误继续执行
}
console.log(3); // 2
return Promise.resolve('4');
}
async1().then((res) => console.log(res)); // 3
console.log('5'); // 同步1
通过 try...catch
捕获 Promise
的拒绝,代码继续执行。输出顺序为 5
、2
、3
、4
。
综合示例
const async1 = async () => {
console.log('1'); // 同步2
setTimeout(() => {
console.log('2'); // 异步2
}, 2000);
await new Promise((resolve) => {
console.log(3); // 同步3
});
// 微任务无状态,后续代码不执行
console.log(4);
return 5;
};
console.log('6'); // 同步1
async1().then((res) => console.log(res)); // 一直等待Promise的值
console.log(7); // 同步4
// Promise
Promise.resolve(1)
// .then必须接收的是函数,忽略
.then(2)
// 忽略
.then(Promise.resolve(3))
// 忽略
.catch(4)
.then((res) => console.log(res)); // 微1 1
setTimeout(() => {
console.log(8); // 异步1
}, 1000);
在这个综合示例中,展示了 async/await
与 Promise
的复杂交互。输出顺序为:
- 输出
6
- 输出
1
- 输出
3
- 输出
7
- 输出
1
(来自Promise.resolve(1)
的微任务) - 输出
5
- 输出
8
- 输出
2
(两秒后) - 输出
4
finally
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
console.log(2); // 3 2
}, 0);
resolve(3);
resolve(4);
})
.then((res) => {
console.log(res); // 1 3
setTimeout(() => {
console.log(p1); //4 Promise fulfilled
}, 1000);
})
.finally((res) => {
// 因为前一个.then没有resolved 或 rejected 返回 undefined
console.log('finally', res); // 2 finally undefined
});
finally
无论 Promise 最终状态如何,都会执行。在此例中,输出顺序为:
- 输出
3
(resolve(3)
优先于resolve(4)
) - 输出
finally undefined
- 输出
1
- 输出
2
- 一秒后输出
Promise { <fulfilled>: 3 }