Promise https://promisesaplus.com/ 是 JavaScript 中的一种异步编程范式, 一个 Promise https://promisesaplus.com/ 对象表示一个即将完成但还未完成的操作. 鉴于 JavaScript 中异步和回调的编程风格, Promise https://promisesaplus.com/ 模式可以有效地避免『Callback Hell』.
Promise 最初有 https://www.npmjs.com/package/q 和 https://www.npmjs.com/package/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 对象
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);
- });
- Promisify
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 https://www.npmjs.com/package/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(...);
- fromCallback
现在我们有了. 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 Promisify
http://mongoosejs.com/ 是 MongoDB 在 JavaScript 下的适配器 (类似 ORM), 提供了模型验证, 数据转换, 业务逻辑钩子, 查询钩子等对象建模工具. http://mongoosejs.com/ 有些 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);
来源: http://www.webhek.com/post/javascript-promise-programming.html