状态
一个 promise 必须处于三种状态之一:pending(待定 )、fulfilled(已兑现 )或 rejected(已拒绝)
如果一个 Promise 已经被兑现或拒绝,即不再处于待定状态,那么则称之为已敲定(settled) 也就是说fulfilled 和rejected 统称为已敲定(settled)
而且promise
一旦敲定(settled)之后,则该promise的状态将不会再变化。即只有pending状态的promise才可以进行状态变化。
初始化
语法:
js
new Promise(executor)
function executor(resolveFunc, rejectFunc) {
// 通常,`executor` 函数用于封装某些接受回调函数作为参数的异步操作
}
-
executor
是同步调用的(在构造Promise
时立即调用),并将resolveFunc
和rejectFunc
函数作为传入参数。这个在事件循环判断执行顺序的时候要记住这点 -
如果
executor
抛出错误,则 Promise 被拒绝(rejected
)。但是,如果 resolveFunc 或 rejectFunc 中的一个已经被调用(此Promise已经被解决),则忽略该错误
如上图,我们直接在executor函数中抛出错误,可以看到promise的状态是rejected
如果在抛出异常之前调用resolveFunc
:
可以看到,确实是忽略了下面的throw语句,promise状态为fulfilled
如果先抛出异常,后resolve呢?
发现跟直接抛出错误是一样的效果,那是因为throw和return类似,都会中断程序,也就不会往下执行
链式调用
Promise.prototype.then()
、Promise.prototype.catch()
和 Promise.prototype.finally()
方法用于将进一步的操作与已敲定的 Promise 相关联。由于这些方法返回 Promise,因此它们可以被链式调用。
也就是只有当前面的Promise敲定(settled)之后 才可能会调用后面.then,.catch,.finally
方法中相应的回调函数(具体的调用时机还要看当前的调用栈和事件队列)。
.then()
方法最多接受两个参数,第一个参数是Promise兑现(fulfilled
)时的回调函数,第二个参数是该promise拒绝时(rejected
)时的回调函数。
js
then(onFulfilled)
then(onFulfilled, onRejected)
如果
onFulfilled
不是一个函数,则内部会被替换为一个恒等 函数((x) => x
),它只是简单地将兑现值向前传也就是
.then(2)
等效于.then((x)=>x)
,传入的对象会被忽略,但仍然会返回一个新的promise对象如果
onRejected
不是一个函数,则内部会被替换为一个抛出器 函数((x) => { throw x; }
),它会抛出它收到的拒绝原因
可以看到我们可以在每个then
方法的onRejected
函数处理错误,不过在实际开发中一般都会在链式调用的最后调用.catch
方法统一处理错误。
并且.then()
方法会立即返回一个新的Promise,且返回的新Promise的状态一定是pending
, 无论当前 Promise 对象的状态如何。
js
const p = new Promise((resolve)=> {
resolve('1')
})
const p2 = p.then((value) => {
console.log(value)
})
console.log(p === p2) // false
console.log(p2) // Promise {<pending>}
为什么是pending
状态?这是因为then
方法的回调函数会被放到微任务队列异步执行,在下一次事件循环时才可能被放置在调用栈执行。感兴趣的可以看下这篇事件循环。
也就是传入 then()
的函数永远不会被同步调用,即使 Promise 已经被解决了(resolved)。所以应该尽可能的将多个同步操作放到一个then方法里完成 ,而不是将多个同步操作放到不同的.then
方法里。
.then
方法返回的 Promise 对象(称之为 p
)的行为取决于处理函数(onFulfilled
和 onRejected
)的执行结果,遵循一组特定的规则
- 返回一个值:
p
以该返回值作为其兑现值。 - 没有返回任何值:
p
以undefined
作为其兑现值。 - 抛出一个错误:
p
抛出的错误作为其拒绝值。 - 返回一个已兑现的 Promise 对象:
p
以该 Promise 的值作为其兑现值。 - 返回一个已拒绝的 Promise 对象:
p
以该 Promise 的值作为其拒绝值。 - 返回另一个待定的 Promise 对象:
p
保持待定(pending)状态,并在该 Promise 对象被兑现/拒绝后立即以该 Promise 的值作为其兑现/拒绝值
catch()
方法内部会调用当前 promise 对象的 then()
方法,并将 undefined
和 onRejected
作为参数传递给 then()
finally()
方法类似于调用 then(onFinally, onFinally)
。然而,有几个不同之处:
-
创建内联函数时,你可以只将其传入一次,而不是强制声明两次或为其创建变量。
-
onFinally
回调函数不接收任何参数。这种情况恰好适用于你不关心拒绝原因或兑现值的情况,因此无需提供它。 -
finally()
调用通常是透明的,不会更改原始 promise 的状态。例如:- 与
Promise.resolve(2).then(() => 77, () => {})
不同,它返回一个最终会兑现为值77
的 promise,而Promise.resolve(2).finally(() => 77)
返回一个最终兑现为值2
的 promise。 - 类似地,与
Promise.reject(3).then(() => {}, () => 88)
不同,它返回一个最终兑现为值88
的 promise,而Promise.reject(3).finally(() => 88)
返回一个最终以原因3
拒绝的 promise
- 与
除了上面的方法,Promise.resolve()
静态方法也是常用的方法之一,语法:
js
Promise.resolve(value)
value值可以是:
-
Promise
对象,将返回该Promise对象jsconst p = new Promise((resolve) => { resolve('1') }) const p2 = Promise.resolve(p) console.log(p === p2) // true
也就是会返回同一个Promise对象,而不是创建一个封装对象
-
thenable 对象,
Promise.resolve()
将调用其then()
方法及其两个回调函数 -
其他值:直接以value兑现
async和await
async
/await
简化了Promise api的使用。
async
语法:
js
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* ..., */ paramN) {
statements
}
异步函数总是返回一个 promise。如果一个异步函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
js
async function foo() {
return 1;
}
function foo() {
return Promise.resolve(1);
}
两种方式比较类似,但也有不一样的点,如果返回的是promise,则async函数会返回一个不同的引用,Promise.resolve则会返回相同的引用。
js
const p = new Promise((res, rej) => {
res(1);
});
async function asyncReturn() {
return p;
}
function basicReturn() {
return Promise.resolve(p);
}
console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false
异步函数的函数体可以被看作是由零个或者多个 await 表达式分割开来的。从顶层代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行 的。因此,不包含 await 表达式的异步函数是同步运行的 。然而,如果函数体内包含 await 表达式,则异步函数就一定会异步完成。
javascript
async function foo() {
await 1;
}
// 等价于
function foo() {
return Promise.resolve(1).then(() => undefined);
}
await
语法:
js
await expression;
expression是要等待的Promise实例,Thenable对象或者任意类型的值。返回从 Promise
实例或 thenable 对象取得的处理结果。如果等待的值不符合 thenable,则返回表达式本身的值。
await
可以拆开Promise的包装,获取其兑现值:await会暂停当前异步函数的执行,在该Promise敲定(settled,兑现或拒绝)之后继续执行。函数执行恢复时,await表达式的值就变成了Promise的兑现值。
若该 Promise
被拒绝(rejected),await
表达式会把拒绝的原因(reason)抛出。当前函数(await
所在的函数)会出现在抛出的错误的栈追踪(stack trace),否则当前函数就不会在栈追踪出现
js
function resolveAfter2Seconds(x) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
let x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
可以看到,await的值就是Promise的兑现值,也就是resolve函数的值。
await 对执行过程的影响
当函数执行到
await
时,被等待的表达式会立即执行,所有依赖该表达式的值的代码会被暂停,并推送进微任务队列(microtask queue)。然后主线程被释放出来,用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise,也会发生这种情况
js
async function foo(name) {
console.log(name, "start");
await console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
也就是await
后面紧跟的表达式( await console.log(name, "middle")
)会被同步的执行 ,await
下面的语句(console.log(name, "end")
)会被放置到微任务队列异步执行 。如果await
后面紧跟的是另外一个异步函数,也会同步的执行直到遇到await
。
对应的Promise
写法:
js
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
resolve(console.log(name, "middle"));
}).then(() => {
console.log(name, "end");
});
}
栈追踪
有时,当异步函数直接返回一个
Promise
时我们会省略await
js
async function noAwait() {
// Some actions...
return /* await */ lastAsyncTask();
}
其实加不加await
,函数noAwait
的兑现值都是一致的。那为什么会建议省略await
?或者加上await
之后会有什么不一样的?
以上面的函数为例,没有await时,noAwait 同步执行完lastAsyncTask函数之后就执行完毕了,noAwait
被pop出调用栈。
如果加上await
,那么noAwait
就会等待lastAsyncTask异步函数兑现之后才能结束。代码就会变成类似于:
js
async function noAwait() {
// Some actions...
const res = await lastAsyncTask();
return res;
}
也就是当执行 lastAsyncTask
函数的时候,如果没有await,当前调用栈只有lastAsyncTask
,如果有await,noAwait,lastAsyncTask
都会出现在调用栈,且lastAsyncTask
优先出栈。
这种区别在错误追踪的时候会有更好的体现,比如lastAsyncTask
函数抛出一个错误时:
js
async function lastAsyncTask() {
await null;
throw new Error("failed");
}
async function noAwait() {
return lastAsyncTask();
}
async function withAwait() {
return await lastAsyncTask();
}
noAwait
函数的栈追踪为:
withAwait
的栈追踪为:
可以看到,使用await可以得到更全面的栈追踪信息,但是,这样会有一点性能牺牲,毕竟 Promise
会被拆装了又再次包装
文章的内容都出自下面的链接并加以自己的理解,建议大家都能看下原文