如何最快做出承诺库
如何最快做出承诺库
原文:https://medium.com/hackernoon/how-to-make-the-fastest-promise-library-f632fd69f3cb
我开发了[Aigle](https://github.com/suguru03/aigle),这是一个快速承诺库。灵感来源于[Bluebir](https://github.com/petkaantonov/bluebird)d。该库不仅是一个基准测试,还是一个实现 Promise A+标准的产品化库,而且比 T2 更快。
什么是承诺?
在解释之前,我想提供一些关于承诺的基本信息。一个承诺可以有三种状态:pending、fulfilled和rejected。一旦状态从pending变为另一个状态,该状态就不能再改变。

Promise states (quote: MDN)
此外,承诺必须总是异步解决,这是最重要的事情之一。
**new** Promise(resolve => resolve(1)) // synchronously
.then(num => {
// called asynchronously
});
高性能库的最佳实践
当我开发库时,我总是遵循这三个原则。
- 避免创建不必要的变量、函数和实例
- 避免执行不必要的功能
- 巧妙处理异步函数
避免创建不必要的变量、函数和实例
遵循这一原则有助于避免不必要的内存分配。举个例子,
**function** sum(array) {
**return** array.reduce(**function** iterator(result, num) {
**return** result **+** num;
});
}
sum([1, 2, 3]); *// 6*
当调用sum时,总是会创建iterator函数。这是一种不必要的内存分配。代码被重写以遵循下一个例子。
**function** iterator(result, num) {
**return** result **+** num;
}
**function** sum(array) {
**return** array.reduce(iterator);
}
sum([1, 2, 3]); *// 6*
代码避免了不必要的函数。
接下来是另一个额外的内存分配的例子。
**function** get(type) {
**const** array **=** [];
**const** object **=** {};
**const** number **=** 0;
**const** string **=** '';
**switch** (type) {
**case** 'array'**:**
**return** array;
**case** 'object'**:**
**return** object;
**case** 'number'**:**
**return** number;
**case** 'string'**:**
**return** string;
}
}
get('string');
在这种情况下,string是唯一必需的变量。array、object和number不需要。代码重写如下:
**function** get(type) {
**switch** (type) {
**case** 'array'**:**
**return** [];
**case** 'object'**:**
**return** {};
**case** 'number'**:**
**return** 0;
**case** 'string'**:**
**return** '';
}
}
get('string');
这两个例子之间没有太大的区别,但是如果在函数中创建实例或函数,就会有很大的区别。
避免执行不必要的功能
例如,在创建 API 时,有必要检查请求参数。
**function** api(req, res) {
**const** { id } **=** req.body;
**if** (**!**isNumber(id)) {
**return** res.sendStatus(400);
}
innerFunc(id)
.then(...)
.**catch**(...)
}
**function** isNumber(id) {
**return** **typeof** id **===** 'number';
}
**function** innerFunc(id) {
**if** (**!**isNumber(id)) {
**return** Promise.reject(**new** Error('error'));
}
...
}
当实现 API 时,最好检查内部函数中的参数,因为该函数可能会被其他函数调用。然而,当实现库时,函数不需要检查参数。在执行内部函数之前,参数已经被检查过了,所以没有必要在内部函数中再次检查它们。
巧妙处理异步函数
我想用Bluebird的例子来解释这个概念。
什么使Bluebird fast?
如果你打开Bluebird库代码,你会看到bitField参数。但是bitField并不那么重要。
Bluebird大致有两种状态:pending与否。处理的最大区别在于两种状态。
如果状态为待定:
**new** Bluebird(**function** executor(resolve) {
// called synchronously
setTimeout(**function** timer() {
resolve(1); *// called asynchronously*
}, 10);
})
.then(**function** onFulfilled(value) {
*// called asynchronously*
});
这个执行顺序是,
- 当新的
Bluebird被调用时,创建Bluebird的父实例 - 执行
executor - 执行
then。then创建Bluebird的子实例 - 执行
resolve - 执行
onFulfilled
当调用then时,一个子实例被创建。那时,resolve还没有被调用,所以父状态是pending。当状态为pending、时,子实例作为子实例链接到父实例。之后[resolve](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L513) 被setTimeout异步调用。然后,[onFulfilled](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L703-L710)T34 被调用,结果为。
当状态为pending时,子实例链接到父实例。调用resolve后,调用onFulfilled。这很简单。
如果状态不是待定:
**new** Bluebird(**function** executor(resolve) {
resolve(); *// called synchronously*
})
.then(**function** onFulfilled(value) {
*// ensured asynchronously*
});
执行顺序是
- 当新的
Bluebird被调用时,创建Bluebird的父实例 - 执行
executor - 执行
resolve - 执行
then。then创建Bluebird的子实例 - 执行
onFulfilled
调用then时,父状态已经不是pending。在这种情况下,如果在没有任何东西的情况下调用onFulfilled,则函数同步执行。出于这个原因,库需要调用一个异步函数。在调用该函数之前,onFulfilled被设置为 a [queue](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L88),这个[schedule](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L159) 函数被称为。Node.js 上的schedule是setImmediate,然后[setImmediate](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L144) 异步调用所有排队的函数。
当状态不是pending时,将onFulfilled置为queue,然后由异步函数执行排队函数。
你可能想知道为什么onFulfilled被设置为queue。这是Bluebird中最聪明的想法。
如何灵活处理异步函数
我想用这个例子来解释一下。
Bluebird.resolve(1) *// synchronously*
.then(num **=>** console.log(num)); *// asynchronously*
Bluebird.resolve(2) *// synchronously*
.then(num **=>** console.log(num)); *// asynchronously*
Bluebird.resolve(3) *// synchronously*
.then(num **=>** console.log(num)); *// asynchronously*
如果没有使用queue,异步函数将被调用三次。但是如果使用了一个queue,这个函数会立刻执行所有排队的函数。我不确定bitField是否会带来很大的好处。但是我觉得Bluebird为什么快是因为简单。
是什么让 Aigle 快速?
我刚刚遵循了这些重要的原则,
- 避免创建不必要的变量、函数和实例
- 避免执行不必要的功能
- 巧妙处理异步函数
如果你遵循它们,你将能够创建好的图书馆。我做了一个基准来检查Aigle和Bluebird之间的性能。基准测试结果在这里。
- 节点 6.9.1
- Aigle v0.5.0
- 蓝鸟 3.5.0 版

Aigle vs Bluebird
Aigle实现非常简单,因此比Bluebird快。如果你对Aigle感兴趣,我希望你能为它做出贡献。
适用于生产环境
最重要的部分是aigle-core依赖。
在Bluebird中,每个 promise 实例都必须是Bluebird的实例。选中实例时,调用instanceof函数。但是Bluebird只检查实例是否是由当前的类**Bluebird**生成的。所以如果用了很多版本,Bluebird会变慢。避免性能损失的关键是拥有相同的依赖性。Aigle有aigle-core依赖,因此每个Aigle实例都由同一个AigleCore类扩展。Aigle会保持高性能。
我想向展示一下基准示例。
$ npm list
[email protected]
├─┬ [email protected] <- [email protected] has [email protected]
│ └── [email protected]
├─┬ [email protected]
│ └── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└─┬ [email protected]
├── [email protected] <- [email protected] has [email protected] too
└── [email protected]
如您所见,不同的aigle依赖项有相同的aigle-core子依赖项。使用[email protected]时,共享aigle-core。
- 节点 6.9.1
Aiglev0.4.0,v0.5.0Bluebird版本 3.4.6,版本 3.5.0
$ node --expose_gc . -t then
======================================
[Aigle] v0.5.0
[Bluebird] v3.5.0
======================================
[promise:then:same] Preparing...
--------------------------------------
[promise:then:same] Executing...
[1] "aigle" 180μs[1.00][1.00]
[2] "bluebird" 341μs[0.526][1.90]
======================================
[promise:then:diff] Preparing...
--------------------------------------
[promise:then:diff] Executing...
[1] "aigle" 178μs[1.00][1.00]
[2] "bluebird" 506μs[0.352][2.84]
promise:then:same使用相同的版本,而promise:then:diff使用不同版本的子实例。Aigle即使版本混杂也不会慢下来。
结论
如果你遵循这三个重要的原则,你会建立一个快速的图书馆。
还有Aigle受[Async](https://github.com/caolan/async)和[Neo-Async](https://github.com/suguru03/neo-async)启发有很多功能。如果你还在使用回调风格,我想鼓励你使用Aigle。
如果我能为Node.js和JavaScript社区做出贡献,我会很高兴成为一名贡献者。
参考
[Aigle](https://github.com/suguru03/aigle)[Bluebird](https://github.com/petkaantonov/bluebird)- MDN



