Promise 是JavaScript中的一种异步编程范式, 一个 Promise 对象表示一个即将完成但还未完成的操作。 鉴于JavaScript中异步和回调的编程风格, Promise 模式可以有效地避免『Callback Hell』。
Promise 最初有 q 和 bluebird 等实现,在ES2015(ES6)提出后Promise已经进入标准,Node.js已经开始支持ES6的很多特性,包括Promise。
传入一个回调函数即可初始化一个Promise对象 padmin
:
var padmin = new Promise(function(resolve, reject){ user.find({role: 'admin'}, function(err, admins){ if(err) reject(err); else resolve(admins); }); });
除此之外,ES6还给出4种常用的初始化方式,下列方法均返回一个Promise对象:
方法 | 说明 |
---|---|
Promise.all(iterable)
|
当 iterable
(比如数组)中所有Promise都 resolve
时,该Promise resolve
; iterable
中任何一个被 reject
,则该Promise被 reject
|
Promise.race(iterable)
|
当 iterable
中任意一个Promise被 resolve
或 reject
,该Promise都会相应地结束 |
Promise.reject(err)
|
直接返回一个被 reject
的Promise对象 |
Promise.reject(value)
|
直接返回一个被 resolve
的Promise对象 |
Promise对象 padmin
拥有两个主要方法:
方法 | 说明 |
---|---|
Promise.prototype.catch(onRejected)
|
当一个Promise被 reject
时调用 onRejected
|
Promise.prototype.then(onFulfilled, onRejected)
|
当一个Promise被resolve时调用 onFulfilled
,被 reject
时调用 onRejected
|
上述两个方法均返回一个Promise,这意味着 .then
和 .catch
可以链式书写。例如:
padmin .then(function(admins){ doSthWith(admins); }) .catch(function(err){ console.error(err); });
在任何一个 then()
回调中抛出的错误都会被后面的 catch()
所截获,以此可以做统一的错误处理:
padmin .then(function(admins){ if(admins === null) throw new Error('query admin error'); return admins.length; }) .then(function(length){ if(length === 0) throw new Error('empty admin list'); console.log(length + ' admins in total.'); }) .catch(function(err){ console.error(err); });
Node.js的内置库以及大量的NPM工具都采用『Error-First Callback』风格,例如:
fs.readFile('foo.txt', function(err, content){ if(err) console.error(err); else console.log(content); });
在Promise风格的代码中,通常会需要 readFile
返回一个Promise对象,于是常常会这样包装该API:
var readFileAsync = function(path){ return new Promise(function(resolve, reject){ fs.readFile(path, function(err, content){ if(err) reject(err); else resolve(content); }); }); } readFileAsync('foo.txt') .then(function(content){ console.log(content): }) .catch(function(err){ console.error(err); });
然而我们需要包装 fs
模块下的所有API :( bluebird
为此提供了有用的方法 promisifyAll()
:
var fs = require("fs"); // 为fs的所有方法创建一个Promise包装,命名为xxxAsync Promise.promisifyAll(fs); fs.readFileAsync("foo.txt").then(...).catch(...);
当然也可以只包装一个函数:
var readFile = Promise.promisify(require("fs").readFile); readFile("foo.txt").then(...).catch(...);
现在我们有了 .promisify
来把一个『Error-First Callback』风格的API包装为Promise风格。 在某些特定情形下,可能每次使用都需要先进行promisify,比如使用后即被销毁的临时对象。 例如从HTTP请求构造的 req
对象每次请求都是新的:
function(req, res, next){ User.find({name: req.body.name}) .then(function(user) { var login = Promise.promisify(req.login); return login.call(req, user); }) .catch(next); }
这时可以用 Promise.fromCallback 方法,直接由『Error-First Callback』调用生成Promise对象,而不需要生成Promise风格的方法。
function(req, res, next){ User.find({name: req.body.name}) .then(function(user) { return BPromise.fromCallback(cb => req.login(user, cb)); }) .catch(next); }
mongoose
是MongoDB在JavaScript下的适配器(类似ORM),提供了模型验证、数据转换、业务逻辑钩子、查询钩子等对象建模工具。 mongoose
有些API(如 .exec()
)会返回内置的Promise,我们可以用一个更强的Promise来替代它:
var BPromise = require('bluebird'); mongoose.Promise = BPromise;
除 exec()
, execPopulate()
系列函数外,mongoose多数API都是回调风格的,通常需要用Bluebird将其Promisify。 这些Mongoose API主要包括下列三类:
Model
. Eg: User.findAsync()
, User.findByIdAsync()
, User.removeAsync()
, User.updateAsync()
Model.prototype
. Eg: user.saveAsync()
, user.removeAsync()
Query.prototype
. Eg: User.find().sortAsync()
, User.find().populateAsync()
BPromise.promisifyAll(mongoose.Model); BPromise.promisifyAll(mongoose.Model.prototype); BPromise.promisifyAll(mongoose.Query.prototype);
这些Promise化的代码最好在代码载入时执行,但不要早于mongoose插件。否则这些插件就不会被Promise化了。
Promise化之后的mongoose用起来是这样的:
var UserSchema = mongoose.Schema({ name: String, phone: String }); var User = mongoose.model('User', UserSchema); User.findAsync() .then(users => console.log(users)); .catch(e => console.error(e));
某些mongoose插件可能需要在Promisify脚本之后执行较为方便。这时我们需要将受影响的模型再次Promise化:
var UserSchema = mongoose.Schema({...}); UserSchema.plugin(require('passport-local-mongoose'), { usernameField: 'phone' }); var User = mongoose.model('User', UserSchema); BPromise.promisifyAll(User);