深入promise
我们可能知道如何使用 Promise
,但是我们知道它们实际上是如何工作的吗?
为了让每个人都了解Promise
,让我们从基础开始。如果我们知道 Promise
是什么以及如何使用它,我们可以跳过这一部分并直接跳到“魔法开始”的地方。
什么是Promise
当有人向我们询问一些我们现在没有的数据时,我们可以Promise
稍后发送或在出现问题时发送错误。
因此Promise
是一个代表异步操作最终完成或失败的对象。
如何创建 Promise
使用Promise
构造函数。
let promise = new Promise(function(resolve, reject) {
// 当Promise初始化后会自动执行这个方法
// 1s后返回done
setTimeout(() => resolve("done"), 1000);
});
传递给构造函数的函数称为“执行器”函数。它包含最终应产生结果的代码。创建new Promise
时,执行器会自动运行。
传递给执行器的resolve
和reject
参数是 JavaScript
提供的函数。当我们准备好提供Promise
结果时,请调用resolve
;如果出现问题,请调用reject
。
Promise
构造函数创建的promise
对象具有以下属性:
state
— 最初为pending
,然后在调用resolve
时更改为fulfilled
,或者在调用reject
时更改为rejected
。result
— 最初为undefined
,然后在调用resolve(value)
时更改为value
,或者在调用reject(error)
时更改为error
。
如何从Promise中获取实际数据?
我们可以使用then
函数订阅承诺中的数据。
promise.then(
function(result) { /* 处理成功的数据 */ },
function(error) { /* 处理异常的数据 */ }
);
then
的第一个参数是一个在 Promise
得到解决并接收结果时运行的函数。
then
的第二个参数是一个在 Promise
被拒绝并收到错误时运行的函数。
then
方法返回一个 Promise
,这就是 Promise
可以被链式调用的原因:
promise.then(
function(result) { return someValue; },
).then(
function(result2) { /* result2的值是someValue */ }
)
Promise 静态方法
Promise
对象有以下静态方法:
Promise.all(iterable)
— 等待所有Promise
得到解决,或者任何Promise
被拒绝。Promise.allSettled(iterable)
— 等待所有Promise
都已被处理(每个Promise
都可以解决或拒绝)。Promise.any(iterable)
— 接受多个Promise
对象,并且一旦对象中的一个Promise
满足,就返回一个用该Promise
的值解析。Promise.race()
— 等待任何Promise
被解决或拒绝。Promise.reject(reason)
— 返回一个新的Promise
对象,该对象因给定原因而被拒绝。Promise.resolve()
— 返回一个用给定值解析的Promise
对象。如果该值是一个Promise
或thenable
对象,则返回该Promise
;否则,返回的promise
将用该值来作为返回。
深入研究
我们将更深入地研究 Promise
的状态如何以及何时从“待处理”更改为“已完成”。
让我们创建一个简单的 Promise
对象,它立即解析一个普通值:
let promise = new Promise(function(resolve, reject) {
resolve(5);
});
console.log(promise);
我们期望看到哪种状态?
没错,这是一个已解决的promise
,值为 5。
现在,请记住Promise.resolve()
用法示例:
Promise.resolve(Promise.resolve(5)) -> returnspromise<fulfilled>(5)
看看以下代码:
let promise = new Promise(function(resolve, reject) {
resolve(Promise.resolve(5));
});
console.log(promise);
我们希望再次看到Promise.resolve()
已解决,对吧?
但是这个打印的promise
将处于“待处理”状态。
现在考虑我们输出与上面示例相同的promise
,但有延迟:
setTimeout(() => console.log(promise), 0);
此时的Promise
现在处于已解决。
让我们总结一下我们的实验结果:
Promise.resolve
返回一个已解决的普通值的promise
。- 当传递的值是已解决的
promise
时,Promise.resolve
返回已解决的promise
。 - 传递给执行器的
resolve
函数返回一个已解决的promise
。 - 当传递的值是已解决的
Promise
时,传递给执行器的resolve
函数会返回一个挂起的Promise
。 - 当传递的值是已解决的
Promise
时,传递给执行器的解析函数的延迟结果是已解决的Promise
。
结论
显然Promise.resolve()
和resolve()
的工作方式不同。
Promise.resolve
和resolve
函数对于普通值的工作方式相同。两歌都返回了最终值。- 当
Promise
作为参数传递时,Promise.resolve
和resolve
函数的工作方式不同。当resolve
以某种奇怪的方式工作时,Promise.resolve
返回作为参数传递的相同的promise
。 resolve
返回待处理Promise
(不管这个Promise
的状态如何),并且可能以某种异步方式更改返回的Promise
状态。
这是否意味着当 Promise
作为参数传递时,resolve
函数会异步工作?
为了理解这一点,让我们看一下 Promise
中肯定是异步工作的部分—— then
、catch
和finally
函数。
我们可能听说过Promise
函数使用的是微任务队列。如果没有,让我们快速提醒一下。
当新的函数订阅 Promise
时,JavaScript
会将其放入微任务队列中。当 Promise
准备好(完成或拒绝)并且引擎不再处理同步代码时,JavaScript
会从微任务队列中逐一获取并调用回调。队列按照 FIFO
规则工作——“先进先出”。一般来说,微任务队列就像一个用于超时等的事件循环,但它具有更高的优先级。
当resolve
函数采用纯值作为参数时,它会立即执行。但是,如果参数是一个Promise
(无论 Promise
的状态是什么),它就会订阅它,并将该函数放入微任务队列中 - 直到 Promise
准备好并且引擎可以自由地执行函数。这就是为什么我们不会立即得到一个已解决的Promise
,而是在setTimeout
中得到它。
规范中描述了此行为,感兴趣的可以去看看。
在本文中,我们试图揭示使用 Promise
的细微差别,并清楚地展示我们可能遇到的问题。