bluebird是一个第三方的Promise类库,并且根据Promise A+规范实现,在ECMAScript 2015的基础上添加了很多扩展方法,如.spread()展开结果集、Promise.promisifyAll()将一个模块的属性方法包装成一个Promise实例。

blueBird的API文档可以查看这里

本文主要列举几个常用的API,方便在熟悉阶段的时候查用。

一. 核心API

new Promise() – 创建实例

new Promise(Function<function resolve, Function reject> resolver)

该方法用于创建一个新的Promise实例,与Promise A+规范的实现一致。传入函数的参数有两个:resolvereject,分别用于执行成功和执行失败时的调用。

.then([function fullfilledHandler]. [function rejectedHandler])

Promise A+规范中的.then()方法相同。

.spread([Function fullfilledHandler], [Function rejectedHandler])

与.then()方法类似,但是上一个回调函数的结果值或者抛出的异常必须是一个数组。这个数组的元素将会依次作为参数传入fulfilledHandler处理函数。

Promise.delay(500).then(() => {
    return [fs.readFileAsync('file1.txt'),
            fs.readFileAsync('file2.txt')];
})
.spread((file1text, file2text) => {
    // ...
});

如果想整个多个不相干但是同时进行的promise,可使用Promise.join()

.catch(Function handler)

从.then()调用链中产生的任何异常都会被传送到最近的.catch()函数中。

这个方法有两个变体:一个用于捕获所有异常,一个用于捕获指定异常。

捕获所有异常:

.catch(function(any error) handler)

捕获部分异常:

.catch(class ErrorClass|function(any error)|Object predicate..., function(any error) handler)

例如:

somePromsie.then(() => {
    return add();
}).catch(TypeError, e => {
    // ...
}).catch(ReferenceError, e => {
    // ...
}).catch(e => {
    // ...
});

.error([reejectHandler])

类似.catch,但是它只捕获operational errors,而不是捕获某一特定类型的异常。

当一个被promise化的函数因为运行中的某些异常被终止执行,将会触发.error()。但是如果这个异常是被throws出来的,只有.catch才能捕获。

.catch(isOperationalError, e => { // ... })

等价于:

.error(e => { // ... })

.finally(Function handler)

无论promise调用链的情况如何,.finally都会在最后被调用。最后的返回值不能在句柄中被改变。

.bind(dynamic thisArg)

创建一个promise对象,这个对象与给定的thisArg绑定,对象内部的this将会指向被绑定的值。由于这个被绑定对象所产生的promise对象也会被绑定到thisArg。

Promise.join(Promise | Tenable | value promises …, Function handler)

用于协调多个并行的promise。当需要处理一个不定数量但是规格一致的多个promise时,.all()是较好的选择。但是当我们需要协调固定数量的离散的promise实例时,Promise.join()是一种更加简单(以及更加优雅)的方法。

let Promise = require('bluebird');
Promise.join(
        getPicture(),
        getComment(), 
        getTweets(), 
        function(pictures, comments, tweets) {
            // ...
        });

Promise.try(Function fn [, Array | dynamic arguments] [, dynamic ctx])

通过Promise.try启动一个promise链,并将所有同步异常封装到promise的reject处理中。

function getUserById(id) {
    return Promise.try(function() {
        if (typeof id !== "number") {
            throw new Error("id must be a number");
        }
        return db.getUserById(id);
    });
}

经过Promise.try封装后,其同步和异步的异常都可以通过.catch来捕获。

Promsie.method(Function fn)

包装指定的函数fn,并使包装后的函数返回一个Promise实例。

Promise.resolve(dynamic value)

将对象包装成resolved状态的Promise实例。

创建一个状态未resolved(已解决)的promise。如果传入的值已经是一个Promise,那么它将直接返回。如果value不可以继续调用(不是thenable对象),则将会返回一个状态未fullfilled(已完成)的Promise,其带有的返回值就是value。

Promise.reject(dynamic reason)

将对象包装成rejected状态的Promise实例。

产生一个状态为rejected(已拒绝)的promise对象,返回值带有reason。

Promise.bind(dynamic thisArg)

Promise.resolue(undefined).bind(thisArg)的语法糖。

二. 集合操作

所有的集合方法在Promise对象中都有一个静态的等价方法。

.all()

将多个Promise实例包装成一个新实例。

promise数组中所有的promise实例均变为resolve时,该方法才会返回resolve的Promise实例,并将所有的结果传递到结果数组中。

如果promise数组中任何一个promise实例为reject的话,则整个Promise.all()调用就会立即终止,并返回一个状态未reject的新promise实例。

var files = [];

for(var i = 0; i < 10; i++) {
    files.push(fs.writeFileAsync('file-' + i + '.txt', '', 'utf-8'));
}
Promise.all(files).then(function(result) {
    // ...
});

.props()

类似.all(),但是针对对象属性的,而不是数组。

当对象属性全部变成执行通过并返回一个resolve状态的promise对象,这个promise对象的fulfillment值是一个拥有原对象键值对应关系的对象。

Promise.props({
    picture: getPictures(),
    comments: getComments(),
    tweets: getTweets()
}).then(result => {
    console.log(result.tweets, result.pictures, result.comments);
});

.settle()

遍历一个包含多个promise的数组,或者一个数组的promise对象。当数组中的所有元素状态均为‘已完成或者‘已拒绝’时,将会返回一个状态‘已完成’的对象。

当有一个promise组成的数组,并且希望知道他们什么时候全部处理完毕,无论最终状态都是‘已完成’还是‘已拒绝’,这个方法将很有用。

.any()

类似于.some(1)。但是终值不是一个数组,而是值本身。执行成功1个promise实例,即返回resolve状态的Promise实例。

.race()

将数组中的非Promise对象,包装成Promise对象。

给定一个数组,当其中任意一个元素状态变成‘已完成’或者‘已拒绝’时,都会立即返回一个Promise,带有终值或者reason。

.some(int count)

成功执行指定次数后返回一个promise实例。这个promise对象的fulfillment值是一个数组,元素是执行最快的那几个promise的终值。

Promise.some([
    ping('ns1.example.com'),
    ping('ns2.example.com'),
    ping('ns3.example.com'),
    ping('ns4.example.com')
], 2).spread((first, second) => {
    // ...
});

如果有太多的promise的状态未‘已拒绝’,以至于最后无法完成,那么将会抛出一个AggregateError错误。

.map(Function mapper [, Object options])

遍历一个数组或者一个数组的promise,对每一个元素使用mapper函数(参数依次为item、idnex、arrayLength)。

如果任何一个promise实例执行失败,则返回状态为reject的Promise实例。

mapper函数会尽快被调用,即当数组的元素对应的promise被解决时。这意味着由最终解决组成的元素顺序不一定和输入时一样。.map()可以用于并发协作。

let fs = Promise.promisifyAll(require('fs'));
fs.readFileSync('.').map(fileName => {
    return JSON.parse
        .catch(SyntaxError, e => {
            e.fileName = fileName;
            throw e;
        });
});

map的静态用法:

let fs = Promise.promisifyAll(require('fs'));
let fileNames = ['file1.txt', 'file2.txt'];
Promise.map(fileNames, fileName => {
    return fs.readFileSync(fileName)
        .then(JSON.parse)
        .catch(SyntaxError, e => {
            e.fileName = fileName;
            throw e;
        });
});

在Options参数对象中,还可以设置concurrency属性,置顶一个并发数。并发数限制了同时执行的Promise数量。

...map(..., {concurrency: 1});

Options.concurrency的默认值为Infinity,不限制并发数。

Promise.map还可以用于替代数组.push + Promise.all方法:

使用数组.push + Promise.all()
var promises = [];
for (var i = 0; i < fileNames.length; ++i) {
    promises.push(fs.readFileAsync(fileNames[i]));
}
Promise.all(promises).then(function() {
    console.log("done");
});

使用Promise.map():

Promise.map(fileNames, function(fileName) {
    return fs.readFileAsync(fileName);
}).then(function() {
    console.log("done");
});

.reduce(Function reducer [, dynamic initialValue])

归约一个数组或一个数组的Promise。归约函数会被传入四个参数:(total, item, index, arrayLength)。

如果归约函数返回了一个promise或者其他含有.then()方法的对象,那么在继续下一个循环之前,这个promise的结果会被延迟。

譬如,使用Promise.reduce计算从三个文件中读取值的总和,每个文件中都有一个数字10:

Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {
  return fs.readFileAsync(fileName, "utf8").then(function(contents) {
    return total + parseInt(contents, 10);
  });
}, 0).then(function(total) {
  //Total is 30
});

.filter(Function filterer [, Object options])

为指定promise数组执行一个过滤函数filterer,并返回经过筛选后promises数组。

一种让下面的过程更加有效率的方法:

var Promise = require("bluebird");
var E = require("core-error-predicates");
var fs = Promise.promisifyAll(require("fs"));

fs.readdirAsync(process.cwd()).filter(function(fileName) {
    return fs.statAsync(fileName)
        .then(function(stat) {
            return stat.isDirectory();
        })
        .catch(E.FileAccessError, function() {
            return false;
        });
}).each(function(directoryName) {
    console.log(directoryName, " is an accessible directory");
});

.each(Fucntion iterator)

依次遍历一个数组或者一个数组的promise。遍历函数会被传入四个参数:(total, item, index, arrayLength)。

如果任何一个promise实例执行失败,则返回状态为reject的Promise实例。

如果遍历函数返回了一个promise或者其他含有.then()方法的对象,那么在继续下一个循环之前,这个promise的结果会被延迟。

// Source: http://jakearchibald.com/2014/es7-async-functions/
function loadStory() {
  return getJSON('story.json')
    .then(function(story) {
      addHtmlToPage(story.heading);
      return story.chapterURLs.map(getJSON);
    })
    .each(function(chapter) { addHtmlToPage(chapter.html); })
    .then(function() { addTextToPage("All done"); })
    .catch(function(err) { addTextToPage("Argh, broken: " + err.message); })
    .then(function() { document.querySelector('.spinner').style.display = 'none'; });
}

三. 函数Promise化

Promise化是指将一个不符合promise规范的API改造成返回promise的API。

在Node.js中,通常的做法是使用promisify来包装非Promise对象,然后就可以使用符合promise规范的方法了。

譬如:

var fs = require('fs');
Promise.promisifyAll(fs);
fs.readFileAsync("file.js", "utf8").then(...)

在fs模块中有一个例外,fs.existsAsync的行为不符合预期,因为fs.exists不会在第一个参数里回调错误。解决方法是使用fs.statAsync。

Promise.promisify(Function nodeFunction [, dynamic receiver])

对给定的nodeFunction进行Promise化。譬如:

var readFile = Promise.promisify(require("fs").readFile);

readFile("myfile.js", "utf8").then(function(contents) {
    return eval(contents);
}).then(function(result) {
    console.log("The result of evaluating myfile.js", result);
}).catch(SyntaxError, function(e) {
    console.log("File had syntax error", e);
}).catch(function(e) {
    console.log("Error reading file", e);
});
var redisGet = Promise.promisify(redisClient.get, redisClient);
redisGet('foo').then(function() {
    //...
});

当Promise化后的nodeFunction有多个返回值,应使用.spread API。

var Promise = require("bluebird");
var request = Promise.promisify(require('request'));
request("http://www.google.com").spread(function(response, body) {
    console.log(body);
}).catch(function(err) {
    console.error(err);
});

Promise.promisifyAll(Object tartget [, Object options])

对tartget对象的所有属性方法进行Promise化。

Promise.promisifyAll(require("redis"));

//Later on, all redis client instances have promise returning functions:

redisClient.hexistsAsync("myhash", "field").then(function(v) {

}).catch(function(e) {

});

Promise.fromNode(Function resolver)

通过使用一个节点风格的回调函数,创建一个resolved的promise。这适合那些不公开类的动态库来实现Promise化,这些不公开类的动态库不能自动使用promisifyAll来实现Promise化。

// TODO use real library that doesn't expose prototypes for promisification
var object = crummyLibrary.create();
Promise.fromNode(function(callback) {
    object.foo("firstArgument", callback);
}).then(function(result) {
    console.log(result);
})

四. 计时器

.delay(int ms)

延时执行。

.timeout(int ms [, String message])

返回一个在指定时间ms后变为失败状态的promise。

五. 同步检查

.isFullfilled()

检查promise是否执行成功。

.isRejected()

检查promise是否执行失败。

.isPending()

检查promise是否在处理中。

.isCancelled()

检查promise是否已取消。

.value()

返回promise的执行结果。

.reason()

返回promise的执行失败的原因。

六. 参考

Bluebird API:http://bluebirdjs.com/docs/api-reference.html

本文作者:子匠_Zijor,转载请注明出处:http://www.dengzhr.com/node-js/1278