一、入门
1、回调地狱(厄运金字塔)
“基于回调”的异步编程风格,对于一个或两个嵌套的调用看起来还不错,但对于一个接一个的多个异步行为,就会进入我们通常所应竭力避免的“回调地狱”。
2、promise(承诺)
2.1、promise对象
Promise 是将“生产者代码(producing code)”和“消费者代码(consuming code)”连接在一起的一个特殊的 JavaScript 对象。
基础构造语法如下:
let promise = new Promise(function(resolve, reject) { // executor(生产者代码) });
promise对象具有以下内部属性:
•state —— 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。
•result —— 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。
executor调用resolve和reject来更新promise的状态和结果,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。
2.2、 .then与.catch
我们通过使用 .then 和 .catch 方法注册消费函数
•.then第一个参数在resolved后执行、第二个参数在rejected后执行
•允许对一个promise对象添加多个.then处理程序
•.catch(errorHandlingFunction)相当于.then(null, errorHandlingFunction)
•.catch() 方法来捕获链中任何环节的错误
// .then
promise1.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
promise1.then((result) => {/* another handler */});
// .catch
promise2.catch(function(error) {});
promise2.then(null, function(error){});
2.3、 .finally
通常用来执行常规清理动作
•.finally(f) 在promise settled 时执行f:无论成功还是失败。
•.finally先于.then或.catch执行
new Promise((resolve, reject) => {
resolve("成功")
})
.finally(() => alert("Promise settled")) // 先触发
.then(result => alert(result)); // <-- .then 显示 "成功"
3、async和await
promise 的处理程序 .then、.catch 和 .finally 都是异步的。
即便一个 promise 立即被 resolve,下面的代码也会先于处理程序执行。
这个涉及到“微任务队列(microtask queue)”,当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序就会被放入队列中,但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。
•队列(queue)是先进先出的:首先进入队列的任务会首先运行。
•只有在 JavaScript 引擎中没有其它任务在运行时,才开始执行任务队列中的任务。
let promise = Promise.resolve();
promise.then(() => alert("promise done!"));
alert("code finished"); // 这个 alert 先显示
Promise.resolve()
.then(() => alert("promise done!"))
.then(() => alert("code finished"));
// 按照预期顺序显示
将后续代码都放到处理程序中,可以达到预期执行效果,但是这种写法有时候不那么合适,async和await关键字提供了更优雅的写法。
•async——确保了函数返回一个 promise
•await——让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待,直到promise settle
alert(result); // done!
}
f();
如果promise 被 reject,await promise将 throw 这个 error。正常情况下,promise 可能需要一点时间后才 reject,我们通常使用try..catch 而不是 .catch来捕获 error。
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
二、进阶
1、promise链
Promise 可以通过 .then() 方法链式调用,每个 .then() 都返回一个新的 promise,可以用来进行一系列的异步操作。
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
在这个链中,如果任何一个 .then() 抛出错误或者返回一个被拒绝(rejected)的 promise,控制流将跳转到 .catch()。
2、Promise API
Promise 类有 6 种静态方法,其中最常用的是Promise.all(),我们着重说一下。
2.1、Promise.all()
Promise.all 接受一个可迭代对象(通常是一个数组项为 promise 的数组),当所有给定的 promise 都 resolve 时,其结果数组将成为返回的新 promise 的结果。而且,数组中元素的顺序与其在源 promise 中的顺序相同。
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3
如果其中一个 promise 被 reject,Promise.all 就会立即被 reject,完全忽略列表中其他的 promise。它们的结果也被忽略。
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("我失败了")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: 我失败了
被 reject 的 error 成为了整个 Promise.all 的结果。
如果我们想要获得所有promises的结果,无论成功还是失败,那么就要用到下面的api ↓
2.2、Promise.allSettled()
Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:
•{status:"fulfilled", value:result} 对于成功的响应,
•{status:"rejected", reason:error} 对于 error。
Promise.allSettled([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("我失败了")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(results => {
results.forEach((result, num) => {
if (result.status == "fulfilled") {
console.log(result.status, result.value);
}
if (result.status == "rejected") {
console.log(result.status, result.reason);
}
});
});
// 打印结果
// fulfilled 1
// rejected Error: 我失败了
// fulfilled 3
2.3、其他api
•Promise.race(promises) —— 等待第一个 settle 的 promise,并将其 result/error 作为结果返回。
•Promise.any(promises)—— 等待第一个 fulfilled 的 promise,并将其结果作为结果返回。如果所有 promise 都 rejected,Promise.any 则会抛出 AggregateError 错误类型的 error 实例(一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error)。
•Promise.resolve(value) —— 使用给定 value 创建一个 resolved 的 promise。
•Promise.reject(error) —— 使用给定 error 创建一个 rejected 的 promise。
3、async与await特殊场景
3.1、async/await 和 Promise.all 一起使用
当我们需要同时等待多个 promise 时,我们可以用 Promise.all 把它们包装起来,然后使用 await:
async function f() {
let results = await Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]);
console.log(results);
};
f(); // [1, 2, 3]
3.2、await兼容非promise
await 允许我们对那些可能不是一个 promise但具有可调用的 then 方法的对象使用。
await 会等待对象.then两个内置函数resolve和reject中的某个被调用,然后使用得到的结果继续执行后续任务。
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
setTimeout(() => resolve(this.num * 2), 1000);
}
}
async function f() {
// 等待 1 秒,之后 result 变为 2
let result = await new Thenable(1);
alert(result); // 2
}
f();
参考文档: zh.javascript.info/async
写在末尾:能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎指正🙏