使用 bluebird 实现更强大的 Promise(1)
- UID
- 1066743
|
使用 bluebird 实现更强大的 Promise(1)
Promise 是 JavaScript 开发中的一个重要概念。按照 Promises/A+ 规范的定义,Promise 表示的是一个异步操作的最终结果。与 Promise 进行交互的方式是通过其 then(onFulfilled, onRejected) 方法,用来注册处理 Promise 最终结果的回调方法。Promise 所表示的异步操作可能成功或失败。当成功时,回调方法 onFulfilled 会接受到一个值作为最终结果;当失败时,回调方法 onRejected 会接受到操作失败的原因。一个 Promise 可能处于三种状态之中:进行中(pending)表示 Promise 所对应的异步操作还在进行中;已满足(fulfilled)表示异步操作已经成功完成;已拒绝(rejected)表示异步操作无法完成。Promise 对象的 then 方法接受两个参数,分别是已满足状态和已拒绝状态的回调方法。已满足状态的回调方法的第一个参数是 Promise 的最终结果值;已拒绝状态的回调方法的第一个参数是 Promise 被拒绝的原因。
Promise 是 ECMAScript 6 规范的一部分,已经有很多的第三方库提供了与 Promise 相关的实现。Promises/A+ 规范只是定义了 then 方法,其目的是为了不同实现库之间的互操作性,侧重的是对于 Promise 的一般使用场景。ECMAScript 6规范中还额外定义了其他 Promise 的方法,包括 Promise.all、Promise.race、Promise.reject 和 Promise.resolve 等。在 NodeJS 和浏览器环境中可以使用不同的 Promise 实现。本文中介绍的 Bluebird 是一个功能丰富而且性能优异的 Promise 实现。
根据不同的运行环境,可以有不同的安装方式。在 NodeJS 应用中,直接使用 npm install bluebird 就可以安装。在浏览器应用中,可以直接下载发布版本的 JavaScript 文件或使用 Bower 来安装。本文的实例基于 NodeJS 环境,对应版本是 LTS 8.9.4 版本,代码使用 ECMAScript 6 的语法。在 NodeJS 环境中,通过 const Promise = require('bluebird') 就可以开始使用 Bluebird 提供的 Promise 对象。
Promise 对象创建在使用 Bluebird 作为 Promise 实现时的第一个问题是如何创建 Promise 对象。实际上,在大部分时候都不需要显式的创建 Promise。很多第三方库本身的方法返回的就已经是 Promise 对象或是包含了 then 方法的 thenable 对象,可以直接使用。Bluebird 的一个实用功能是把不使用 Promise 的已有API包装成返回 Promise 的新 API。大部分 NodeJS 的标准库 API 和不少第三方库的 API 都使用了回调方法的模式,也就是在执行异步操作时,需要传入一个回调方法来接受操作的执行结果和可能出现的错误。对于这样的方法,Bluebird 可以很容易的将它们转换成使用 Promise 的形式。
比如 NodeJS 中读取文件的 fs.readFile 方法,其常见的使用模式如 所示。使用回调方法的问题在于可能导致过多的代码嵌套层次,造成所谓的回调地狱问题(callback hell)。
清单1. NodeJS 中的 fs.readFile 方法的基本使用方式1
2
3
4
5
6
7
8
9
10
11
| const fs = require('fs'),
path = require('path');
fs.readFile(path.join(__dirname, 'sample.txt'), 'utf-8', (err, data) => {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
|
Bluebird 的 Promise.promisifyAll 方法可以为一个对象的属性中的所有方法创建一个对应的使用 Promise 的版本。这些新创建方法的名称在已有方法的名称后加上"Async"后缀。代码清单1中的实现可以改成代码清单2中的使用 Promise 的形式。新的方法 readFileAsync 对应的是已有的 readFile 方法,但是返回值是 Promise 对象。除了 readFile,fs 中的其他方法也都有了对应的 Async 版本,如 writeFileAsync 和 fstatAsync 等。
清单2. 使用 Promise.promisifyAll 来转换方法1
2
3
4
5
6
7
8
9
| const Promise = require('bluebird'),
fs = require('fs'),
path = require('path');
Promise.promisifyAll(fs);
fs.readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8')
.then(data => console.log(data))
.catch(err => console.error(err));
|
如果不希望把一个对象的所有方法都自动转换成使用 Promise 的形式,可以使用 Promise.promisify 来转换单个方法,如 Promise.promisify(require("fs").readFile)。对于一个 NodeJS 格式的回调方法,可以使用 Promise.fromCallback 将其转换成一个 Promise。回调方法的结果决定了 Promise 的状态。
对于一个已有值,可以使用 Promise.resolve 方法将其转换成一个状态为已满足的 Promise 对象。同样的,使用 Promise.reject 方法可以从给定的拒绝原因中创建一个状态为已拒绝的 Promise 对象。这两个方法都可以快速地创建 Promise 对象。
如果上述这些方法都不能满足创建 Promise 的需求,可以使用 Promise 构造方法。构造方法的参数是一个接受两个方法作为参数的方法,这两个方法分别用来把 Promise 标记为已满足或已拒绝。在代码清单3中,创建的 Promise 的状态取决于生成的随机数是否大于0.5。
清单3. 创建 Promise1
2
3
4
5
6
7
8
9
10
| const Promise = require('bluebird');
const promise = new Promise((resolve, reject) => {
const value = Math.random();
if (value > 0.5) {
resolve(value);
} else {
reject(`Invalid value ${value}`);
}
});
promise.then(console.log).catch(console.error);
|
|
|
|
|
|
|