ES6之Promise 與 Node.js 8新特性之util.promisify()

2017年五月底Node.js 8正式發(fā)布,帶來了 很多新特性 侦铜。本文討論下util.promisify()這個(gè)方法。

Promise

介紹promisify之前,首先來看下Promise這個(gè)API君纫,因?yàn)閡til.promisify()這個(gè)方法就是把原來的異步回調(diào)方法改成支持 Promise 的方法并返回一個(gè)Promise實(shí)例。ES2015(ES6)加入了 Promise芹彬,可以直接使用蓄髓。Promise沒有新的語法元素,即使在不支持原生Promise的環(huán)境里也可以使用舒帮,比如 Q 或者 Bluebird会喝,甚至 jQuery ,在小程序里有效玩郊。

ES2017 增加了 await/async 語法肢执,但請注意, await 后面必須跟Promise實(shí)例才能實(shí)現(xiàn)異步瓦宜。

由于歷史原因蔚万,js中存在大量異步回調(diào),回調(diào)層數(shù)多的話就形成了“地獄回調(diào)”临庇,不僅代碼丑陋反璃,也很難維護(hù)。針對這種現(xiàn)象假夺,開發(fā)社區(qū)總結(jié)出來一套名為 Promise/A+ 的解決方案淮蜈。大體上來說,這套方案通過使用 “Promise 回調(diào)實(shí)例”包裹原先的回調(diào)函數(shù)已卷,可以將原先復(fù)雜的嵌套展開梧田、鋪平,從而降低開發(fā)和維護(hù)的難度和成本1。下面來自文獻(xiàn)1的代碼非常簡單清晰裁眯、一目了然:

new Promise( (resolve, reject) => { // 構(gòu)建一個(gè) Promise 實(shí)例
  someAsyncFunction( (err, result) => { // 調(diào)用原來的異步函數(shù)
    if (err) { // 發(fā)生錯(cuò)誤鹉梨,進(jìn)入錯(cuò)誤處理模式
      return reject(err);
    }
    resolve(result); // 一切正常,進(jìn)入隊(duì)列的下一環(huán)節(jié)
  });
})
  .then( result => { // 下一環(huán)節(jié)
    return doSomething(result);
  })
  .then( result2 => { // 又下一環(huán)節(jié)
    return doSomething2(result2);
  })
  ... // 各種中間環(huán)節(jié)
  .catch( err => { // 錯(cuò)誤處理
    console.log(err);
  });

ES6規(guī)定穿稳,Promise對象是一個(gè)構(gòu)造函數(shù)存皂,用來生成Promise實(shí)例。下面代碼創(chuàng)造了一個(gè)Promise實(shí)例逢艘。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

假設(shè)一個(gè)事件eventFor2Seconds操作耗時(shí)2秒旦袋,那么我們可以使用Promise這樣寫:

function eventFor2Seconds(x) {//eventFor2Seconds方法返回一個(gè)Promise實(shí)例代表一個(gè)異步操作
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}
async function doSomething() {
  var x = await eventFor2Seconds(10);//需要使用await實(shí)現(xiàn)異步
  console.log(x); // 2秒后打印 10
}
doSomething();

Promise有以下幾個(gè)特點(diǎn)2

  1. Promise對象有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成它改,又稱 Fulfilled)和Rejected(已失敯淘小)且對象的狀態(tài)不受外界影響。只有異步操作的結(jié)果央拖,可以決定哪一種狀態(tài)祭阀,任何其他操作都無法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來爬泥,它的英語意思就是“承諾”柬讨,表示其他手段無法改變。
  2. 一旦狀態(tài)生成袍啡,就不會(huì)再變踩官,任何時(shí)候都得到這個(gè)狀態(tài)。Promise對象只有兩種可能的狀態(tài)改變方式:從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected境输。只要這兩種情況發(fā)生蔗牡,狀態(tài)就凝固了,不會(huì)再變了嗅剖,會(huì)一直保持這個(gè)結(jié)果辩越。就算改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù)信粮,也會(huì)立即得到這個(gè)結(jié)果黔攒。這與事件(Event)完全不同,事件的特點(diǎn)是强缘,如果你錯(cuò)過了它督惰,再去監(jiān)聽,是得不到結(jié)果的旅掂。
  3. Promise所有API都返回當(dāng)前實(shí)例(就是builder設(shè)計(jì)模式)赏胚,因此可以采用連續(xù)的then/catch鏈?zhǔn)讲僮鱽韺懟卣{(diào)。
  4. resolve方法會(huì)使之后的連續(xù)then執(zhí)行(不寫then也沒事)商虐,reject方法會(huì)使之后的catch執(zhí)行(如果不寫catch會(huì)出現(xiàn)異常觉阅,因此catch必須寫)崖疤。
  5. 可以在then中return出數(shù)據(jù),并且這個(gè)數(shù)據(jù)會(huì)以參數(shù)的形式傳入下一個(gè)then典勇。
  6. Promise 對象的錯(cuò)誤具有“冒泡”性質(zhì)劫哼,會(huì)一直向后傳遞,直到被捕獲為止割笙。也就是說沦偎,錯(cuò)誤總是會(huì)被下一個(gè)catch語句捕獲。

Promise對象提供統(tǒng)一的接口咳蔚,包括resolve,reject搔驼,then谈火,catch,all舌涨,race糯耍,使得異步流程控制更加方便。

Promise.all()
Promise.all方法用于將多個(gè)Promise實(shí)例囊嘉,包裝成一個(gè)新的Promise實(shí)例温技。var p = Promise.all([p1, p2, p3]);all()接受數(shù)組作為參數(shù)。p1,p2,p3都是Promise的實(shí)例對象扭粱,p要變成Resolved狀態(tài)需要p1舵鳞,p2,p3狀態(tài)都是Resolved琢蛤,如果p1,p2,p3至少有一個(gè)狀態(tài)是Rejected蜓堕,p的狀態(tài)就變成Rejected(很像&&符號(hào)鏈接)

Promise.race()
var p = Promise.race( [p1,p2,p3] )上面代碼中,只要p1博其、p2套才、p3之中有一個(gè)實(shí)例首先改變狀態(tài),p的狀態(tài)就跟著改變慕淡。那個(gè)首先改變的Promise 實(shí)例的返回值背伴,就傳遞給p的回調(diào)函數(shù)(很像||符號(hào)操作)

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});
Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

Promise resolve()
resolve可以將將現(xiàn)有對象轉(zhuǎn)為Promise對象。

Promise.resolve('fa')
// 等價(jià)于
new Promise(resolve => resolve('fa'))

Promise reject()
Promise.reject(reason)方法也會(huì)返回一個(gè)新的 Promise 實(shí)例峰髓,該實(shí)例的狀態(tài)為rejected傻寂。

Promise.reject('foo')
// 等價(jià)于
new Promise(reject => reject('foo'))


util.promisify

Promise的用法差不多就這些了,下面來看下util.promisify的用法儿普。我的理解是util.promisify只是返回一個(gè)Promise實(shí)例來方便異步操作崎逃。比如要延遲一段時(shí)間執(zhí)行代碼,我們可以這樣:

let { promisify } = require('util')
const sleep = promisify(setTimeout)
async function fuc() {
  console.log('before')
  await sleep(2000)
  console.log('after')
}
fuc()

上面的代碼結(jié)合while(1)死循環(huán)可以實(shí)現(xiàn)一個(gè)簡單的定時(shí)器功能眉孩。

大家都知道nodejs的fs庫有讀文件的API个绍,結(jié)合util.promisify使用鏈?zhǔn)讲僮鞔娴鬲z回調(diào):

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.
 });

在綁定的函數(shù)的參數(shù)列表中的最后會(huì)多出一個(gè)參數(shù)勒葱,這個(gè)參數(shù)是函數(shù)而且包含兩個(gè)參數(shù)為 (err, result),前面是可能的錯(cuò)誤巴柿,后面是正常的結(jié)果凛虽。這個(gè)多出來的參數(shù)是promisify在綁定的時(shí)候強(qiáng)制添加的作為默認(rèn)的回調(diào)函數(shù),這個(gè)默認(rèn)的回調(diào)函數(shù)源碼如下:

(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]);
  }
}

然后再看一個(gè)例子:

function paramObj(params, callback) {//假設(shè)業(yè)務(wù)需求這個(gè)函數(shù)需要傳入一個(gè)對象參數(shù)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會(huì)添加一個(gè)自己風(fēng)格的類型為function的參數(shù)
  }
}

async function test() {//async會(huì)返回一個(gè)Promise實(shí)例
  var data = await promisify(paramObj).bind(paramObj)()//不傳入一個(gè)對象的參數(shù)的話會(huì)拋出異常
  console.log('inner data: ', data)
  return data
}
test()
  .then(data => {
    console.log('outer data:', data)
  })
  .catch(err => {
    console.log('outer err:', err)
  })



參考文獻(xiàn)
  1. Node.js 8 中的 util.promisify的詳解
  2. 淺析Promise用法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钉迷,隨后出現(xiàn)的幾起案子至非,更是在濱河造成了極大的恐慌,老刑警劉巖糠聪,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荒椭,死亡現(xiàn)場離奇詭異,居然都是意外死亡舰蟆,警方通過查閱死者的電腦和手機(jī)趣惠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來身害,“玉大人味悄,你說我怎么就攤上這事∷欤” “怎么了侍瑟?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長界赔。 經(jīng)常有香客問我丢习,道長,這世上最難降的妖魔是什么淮悼? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任咐低,我火速辦了婚禮,結(jié)果婚禮上袜腥,老公的妹妹穿的比我還像新娘见擦。我一直安慰自己,他們只是感情好羹令,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布鲤屡。 她就那樣靜靜地躺著,像睡著了一般福侈。 火紅的嫁衣襯著肌膚如雪酒来。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天肪凛,我揣著相機(jī)與錄音堰汉,去河邊找鬼辽社。 笑死,一個(gè)胖子當(dāng)著我的面吹牛翘鸭,可吹牛的內(nèi)容都是我干的滴铅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼就乓,長吁一口氣:“原來是場噩夢啊……” “哼汉匙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起生蚁,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對情侶失蹤噩翠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后邦投,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绎秒,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年尼摹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂娄。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蠢涝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阅懦,到底是詐尸還是另有隱情和二,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布耳胎,位于F島的核電站惯吕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏怕午。R本人自食惡果不足惜废登,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郁惜。 院中可真熱鬧堡距,春花似錦、人聲如沸兆蕉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虎韵。三九已至易稠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間包蓝,已是汗流浹背驶社。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工企量, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衬吆。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓梁钾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逊抡。 傳聞我的和親對象是個(gè)殘疾皇子姆泻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Promiese 簡單說就是一個(gè)容器,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果冒嫡,語法上說拇勃,Pr...
    雨飛飛雨閱讀 3,357評(píng)論 0 19
  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,705評(píng)論 1 56
  • Promise含義 Promise是異步編程的一種解決方案孝凌,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更強(qiáng)大方咆。所謂Pr...
    oWSQo閱讀 1,085評(píng)論 0 4
  • 1. Promise 的含義 所謂Promise,簡單說就是一個(gè)容器蟀架,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)...
    ROBIN2015閱讀 490評(píng)論 0 0
  • https://leetcode.com/problems/path-sum-iii/#/description ...
    Zihowe閱讀 379評(píng)論 0 0