2017年五月底Node.js 8正式发布,带来了 很多新特性 。本文讨论下util.promisify()这个方法。
Promise
介绍promisify之前,首先来看下Promise这个API,因为util.promisify()这个方法就是把原来的异步回调方法改成支持 Promise 的方法并返回一个Promise实例。ES2015(ES6)加入了 Promise,可以直接使用。Promise没有新的语法元素,即使在不支持原生Promise的环境里也可以使用,比如 Q 或者 Bluebird,甚至 jQuery ,在小程序里有效。
ES2017 增加了 await/async 语法,但请注意, await 后面必须跟Promise实例才能实现异步。
由于历史原因,js中存在大量异步回调,回调层数多的话就形成了“地狱回调”,不仅代码丑陋,也很难维护。针对这种现象,开发社区总结出来一套名为 Promise/A+ 的解决方案。大体上来说,这套方案通过使用 “Promise 回调实例”包裹原先的回调函数,可以将原先复杂的嵌套展开、铺平,从而降低开发和维护的难度和成本1。下面来自文献1的代码非常简单清晰、一目了然:
new Promise( (resolve, reject) => { // 构建一个 Promise 实例 someAsyncFunction( (err, result) => { // 调用原来的异步函数 if (err) { // 发生错误,进入错误处理模式 return reject(err); } resolve(result); // 一切正常,进入队列的下一环节 }); }) .then( result => { // 下一环节 return doSomething(result); }) .then( result2 => { // 又下一环节 return doSomething2(result2); }) ... // 各种中间环节 .catch( err => { // 错误处理 console.log(err); });
假设一个事件eventFor2Seconds操作耗时2秒,那么我们可以使用Promise这样写:
function eventFor2Seconds(x) {//eventFor2Seconds方法返回一个Promise实例代表一个异步操作 return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); }async function doSomething() { var x = await eventFor2Seconds(10);//需要使用await实现异步 console.log(x); // 2秒后打印 10} doSomething();
Promise有以下几个特点2:
Promise对象有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)且对象的状态不受外界影响。只有异步操作的结果,可以决定哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
一旦状态生成,就不会再变,任何时候都得到这个状态。Promise对象只有两种可能的状态改变方式:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise所有API都返回当前实例(就是builder设计模式),因此可以采用连续的then/catch链式操作来写回调。
resolve方法会使之后的连续then执行(不写then也没事),reject方法会使之后的catch执行(如果不写catch会出现异常,因此catch必须写)。
可以在then中return出数据,并且这个数据会以参数的形式传入下一个then。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
Promise对象提供统一的接口,包括resolve,reject,then,catch,all,race,使得异步流程控制更加方便。
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。var p = Promise.all([p1, p2, p3]);
all()接受数组作为参数。p1,p2,p3都是Promise的实例对象,p要变成Resolved状态需要p1,p2,p3状态都是Resolved,如果p1,p2,p3至少有一个状态是Rejected,p的状态就变成Rejected(很像&&符号链接)
Promise.race()var p = Promise.race( [p1,p2,p3] )
上面代码中,只要p1、p2、p3之中有一个实例首先改变状态,p的状态就跟着改变。那个首先改变的Promise 实例的返回值,就传递给p的回调函数(很像||符号操作)
Promise resolve()
resolve可以将将现有对象转为Promise对象。
Promise.resolve('fa')// 等价于new Promise(resolve => resolve('fa'))
Promise reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
Promise.reject('foo')// 等价于new Promise(reject => reject('foo'))
util.promisify
Promise的用法差不多就这些了,下面来看下util.promisify的用法。我的理解是util.promisify只是返回一个Promise实例来方便异步操作。比如要延迟一段时间执行代码,我们可以这样:
let { promisify } = require('util')const sleep = promisify(setTimeout)async function fuc() { console.log('before') await sleep(2000) console.log('after') } fuc()
上面的代码结合while(1)死循环可以实现一个简单的定时器功能。
大家都知道nodejs的fs库有读文件的API,结合util.promisify使用链式操作代替地狱回调:
const util = require('util');const fs = require('fs');const stat = util.promisify(fs.stat); stat('.') .then((stats) => { // Do something with `stats` }) .catch((error) => { // Handle the error. });
在绑定的函数的参数列表中的最后会多出一个参数,这个参数是函数而且包含两个参数为 (err, result),前面是可能的错误,后面是正常的结果。这个多出来的参数是promisify在绑定的时候强制添加的作为默认的回调函数,这个默认的回调函数源码如下:
(err, ...values) => { if (err) { promiseReject(promise, err); } else if (argumentNames !== undefined && values.length > 1) { const obj = {}; for (var i = 0; i < argumentNames.length; i++) obj[argumentNames[i]] = values[i]; promiseResolve(promise, obj); } else { promiseResolve(promise, values[0]); } }
然后再看一个例子:
function paramObj(params, callback) {//假设业务需求这个函数需要传入一个对象参数params才是正常的,否则就表示异常 console.log('params', params) console.log('callback', callback) if (typeof params != 'object') { params(JSON.stringify({ "code": "1", "msg": "params null" }), null)//抛出异常,原因是params null } else { callback(null, params)//promisify会添加一个自己风格的类型为function的参数 } }async function test() {//async会返回一个Promise实例 var data = await promisify(paramObj).bind(paramObj)()//不传入一个对象的参数的话会抛出异常 console.log('inner data: ', data) return data } test() .then(data => { console.log('outer data:', data) }) .catch(err => { console.log('outer err:', err) })
参考文献
作者:宛丘之上兮
链接:https://www.jianshu.com/p/139ef018281d