跳到主要内容

异步编程详解

同步任务、微任务、异步任务

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

在这段代码中,执行顺序如下:

  1. console.log(3) 输出 3
  2. console.log(7) 输出 7
  3. 设置一个 setTimeout,将 console.log(5)resolve(6) 放入异步任务队列
  4. resolve(1) 立即执行,随后 resolve(2) 也立即执行
  5. console.log(4) 输出 4
  6. 微任务队列中的 p.then 先执行,输出 1
  7. 最后,first().then 执行,输出 2
  8. 异步任务中的 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

此代码展示了微任务的执行顺序:

  1. 输出 promise1
  2. 输出 undefinedPromise fulfilled
  3. 输出 end
  4. 微任务队列依次执行,输出 promise2promise3promise4
  5. await b 完成后,输出 after1
  6. 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 函数中的代码同步执行,输出顺序为 1234

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 完成后继续。输出顺序为 14523

异步任务

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. 输出 1
  2. 输出 5
  3. 输出 7
  4. 微任务输出 2
  5. 异步任务按注册顺序输出 436

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 后的代码不会执行。输出顺序为 5126

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

执行顺序:

  1. 输出 6
  2. 输出 1
  3. 输出 2
  4. 输出 7
  5. 微任务输出 3
  6. 微任务输出 4
  7. 微任务输出 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);
});

输出顺序:

  1. 输出 6
  2. 输出 1
  3. 输出 2
  4. 输出 7
  5. 微任务输出 4
  6. 微任务输出 5
  7. 异步任务输出 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

执行顺序:

  1. 输出 5
  2. 输出 1
  3. 输出 6
  4. 输出 3
  5. 输出 8
  6. 微任务输出 2
  7. 微任务输出 7
  8. 微任务输出 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 的拒绝,代码继续执行。输出顺序为 5234

综合示例

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/awaitPromise 的复杂交互。输出顺序为:

  1. 输出 6
  2. 输出 1
  3. 输出 3
  4. 输出 7
  5. 输出 1(来自 Promise.resolve(1) 的微任务)
  6. 输出 5
  7. 输出 8
  8. 输出 2(两秒后)
  9. 输出 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 最终状态如何,都会执行。在此例中,输出顺序为:

  1. 输出 3resolve(3) 优先于 resolve(4))
  2. 输出 finally undefined
  3. 输出 1
  4. 输出 2
  5. 一秒后输出 Promise { <fulfilled>: 3 }