从头开始编写自己的Node.js Promise库
Promise是JavaScript中首选的异步原语。回调变得越来越不常见,尤其是在Node.js中可以使用async/await的情况下。async/await是基于promise的,所以您需要了解promise以掌握async/await。在本文中,我将引导您编写自己的Promise库,并演示如何在async/await中使用它。
什么是promise?
在ES6规范中,promise是一个类,其构造executor
函数带有一个函数。Promise类的实例具有一个then()
函数。根据规范,Promise具有其他几个属性,但是出于本教程的目的,您可以忽略它们。下面是一个简单MyPromise
类的脚手架。
class MyPromise {
//`executor`具有两个参数,分别为`resolve()`和`reject()`。
//executor函数负责调用`resolve()`或`reject()`表示
//异步操作成功(已解决)或失败(已拒绝)。
constructor(executor) {}
//如果实现了promise,则会调用onFulfilled
// 如果promise被拒绝, onRejected会被调用
then(onFulfilled, onRejected) {}
}
该executor
函数有2个参数,resolve()
和reject()
。一个promise是一个具有3个状态的状态:
- pending:初始状态,表示尚未兑现promise
- fulfilled:基础操作成功且具有关联值
- rejected:基础操作失败,并且promise存在关联的错误
考虑到这一点,实现MyPromise
构造函数的第一次迭代很简单。
constructor(executor) {
if (typeof executor !== 'function') {
throw new Error('Executor must be a function');
}
//内部状态。 $state是承诺的状态,而$chained是
//实现promise后,我们需要调用的函数数组。
this.$state = 'PENDING';
this.$chained = [];
// Implement `resolve()` and `reject()` for the executor function to use
const resolve = res => {
// A promise is considered "settled" when it is no longer
// pending, that is, when either `resolve()` or `reject()`
// was called once. Calling `resolve()` or `reject()` twice
// or calling `reject()` after `resolve()` was already called
// are no-ops.
if (this.$state !== 'PENDING') {
return;
}
// There's a subtle difference between 'fulfilled' and 'resolved'
// that you'll learn about later.
this.$state = 'FULFILLED';
this.$internalValue = res;
// If somebody called `.then()` while this promise was pending, need
// to call their `onFulfilled()` function
for (const { onFulfilled } of this.$chained) {
onFulfilled(res);
}
};
const reject = err => {
if (this.$state !== 'PENDING') {
return;
}
this.$state = 'REJECTED';
this.$internalValue = err;
for (const { onRejected } of this.$chained) {
onRejected(err);
}
};
// Call the executor function with `resolve()` and `reject()` as in the spec.
try {
// If the executor function throws a sync exception, we consider that
// a rejection. Keep in mind that, since `resolve()` or `reject()` can
// only be called once, a function that synchronously calls `resolve()`
// and then throws will lead to a fulfilled promise and a swallowed error
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
该then()
功能更加容易。请记住,该then()
函数有两个参数,onFulfilled()
和onRejected()
。该then()
函数负责确保onFulfilled()
是否实现onRejected()
了promise以及是否rejected了promise。如果promise已经解决或被rejected,then()
应立即调用onFulfilled()
或onRejected()
*。如果Promise仍未完成,then()
则应将函数推入$chained
数组,以便resolve()
andreject()
函数可以调用它们。
then(onFulfilled, onRejected) {
if (this.$state === 'FULFILLED') {
onFulfilled(this.$internalValue);
} else if (this.$state === 'REJECTED') {
onRejected(this.$internalValue);
} else {
this.$chained.push({ onFulfilled, onRejected });
}
}
ES6规范说,调用.then()
已解决或被rejected的Promise意味着onFulfilled()
或onRejected()
应在下一个事件周期上调用。由于本文的代码仅是一个教学示例,而不是该规范的确切实现,因此该实现将忽略此详细信息。
promise链(GitHub Gist)
上面的示例专门忽略了Promise最复杂,最有用的部分:chaining。链的想法是,如果onFulfilled()
oronRejected()
函数返回一个promise,then()
则应返回一个“锁定”以匹配返回的promise状态的新promise。例如:
p = new MyPromise(resolve => {
setTimeout(() => resolve('World'), 100);
});
p.
then(res => new MyPromise(resolve => resolve(`Hello, ${res}`))).
// Will print out 'Hello, World' after approximately 100ms
then(res => console.log(res));
下面是.then()
返回promise的新函数,因此您可以进行链接。
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// Ensure that errors in `onFulfilled()` and `onRejected()` reject the
// returned promise, otherwise they'll crash the process. Also, ensure
// that the promise
const _onFulfilled = res => {
try {
// If `onFulfilled()` returns a promise, trust `resolve()` to handle
// it correctly.
resolve(onFulfilled(res));
} catch (err) {
reject(err);
}
};
const _onRejected = err => {
try {
reject(onRejected(err));
} catch (_err) {
reject(_err);
}
};
if (this.$state === 'FULFILLED') {
_onFulfilled(this.$internalValue);
} else if (this.$state === 'REJECTED') {
_onRejected(this.$internalValue);
} else {
this.$chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected });
}
});
}
现在then()
返回一个promise。但是,仍有一些工作要做:如果onFulfilled()
返回promise,则resolve()
需要能够处理它。为了支持此resolve()
功能,该功能将需要then()
在两步递归舞蹈中使用。以下是扩展resolve()
功能。
const resolve = res => {
// A promise is considered "settled" when it is no longer
// pending, that is, when either `resolve()` or `reject()`
// was called once. Calling `resolve()` or `reject()` twice
// or calling `reject()` after `resolve()` was already called
// are no-ops.
if (this.$state !== 'PENDING') {
return;
}
// If `res` is a "thenable", lock in this promise to match the
// resolved or rejected state of the thenable.
const then = res != null ? res.then : null;
if (typeof then === 'function') {
// In this case, the promise is "resolved", but still in the 'PENDING'
// state. This is what the ES6 spec means when it says "A resolved promise
// may be pending, fulfilled or rejected" in
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects
return then(resolve, reject);
}
this.$state = 'FULFILLED';
this.$internalValue = res;
// If somebody called `.then()` while this promise was pending, need
// to call their `onFulfilled()` function
for (const { onFulfilled } of this.$chained) {
onFulfilled(res);
}
return res;
};
为了简单起见,上面的示例省略了关键细节,即一旦“promise”被“锁定”以匹配另一个promise,调用resolve()
或reject()
为“无操作”,该细节便会消失。在上面的示例中,您可以resolve()
向未决的Promise抛出错误,然后在res.then(resolve, reject)
上面没有操作。这仅是示例,而不是ES6 Promise规范的完整实现。
上面的代码说明了“已解决”promise和“已实现”promise之间的区别。这种区别是微妙的,与promise链有关。“已解决”不是实际的promise状态,而是ES6规范中定义的术语。当您致电resolve()
promise被视为已解决时,可能会发生以下两种情况之一:
- 如果您
resolve(v)
在哪里v
不是promise,那么promise将立即变为现实。在这种简单情况下,“解决”和“实现”是同一回事。 - 如果您
resolve(v)
在v
另一个promise中调用,那么promise会一直pending,直到v
解决或rejected为止。在这种情况下,promise已“解决”,但仍未完成。
使用async/await
请记住,await
关键字会暂停async
函数的执行,直到等待的promise已解决为止。现在您有了一个基本的自制Promise库,让我们看看将其与async/await一起使用时会发生什么。console.log()
向该then()
函数添加一条语句:
then(onFulfilled, onRejected) {
console.log('Then', onFulfilled, onRejected, new Error().stack);
return new MyPromise((resolve, reject) => {
/* ... */
});
}
现在,让我们来看await
一个实例,MyPromise
看看会发生什么。
run().catch(error => console.error(error.stack));
async function run() {
const start = Date.now();
await new MyPromise(resolve => setTimeout(() => resolve(), 100));
console.log('Elapsed time', Date.now() - start);
}
注意.catch()
上面的call。该catch()
功能是ES6 Promise规范的核心部分。本文将不涉及太多细节,因为.catch(f)
它等效于.then(null, f)
,因此没有太多内容。
以下是输出。注意,await.then()
使用onFulfilled()
和隐式调用,并onRejected()
深入到V8的C ++内部。另外,await
始终等到下一个滴答声再致电.then()
。
Then function () { [native code] } function () { [native code] } Error
at MyPromise.then (/home/val/test/promise.js:63:50)
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:686:11)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:608:3
Elapsed time 102
总结
async/await功能强大,但除非您了解promise的基础知识,否则很难掌握。promise有很多细微差别,例如执行程序函数中的同步错误被捕获,以及promise一旦解决就无法更改状态的事实,这使得async/await成为可能。一旦对promise有了深刻的了解,async/await就变得容易多了。