Node.js 8 中的 `util.promisify`

Node.js 8 於上個月月底正式發佈,帶來了不少新特性。其中比較值得注意的,便有 util.promisify() 這個方法。javascript

若是你已經很熟悉 Promise,請繼續往下看。若是你還不熟悉 Promise,能夠先跳過去看下下章:Promise 介紹html

util.promisify()

雖然 Promise 已經普及,可是 Node.js 裏仍然有大量依賴回調的異步函數,若是咱們把每一個函數都封裝一遍,那真是齁麻煩齁麻煩的,比齁還麻煩。java

因此 Node.js 8 就提供了 util.promisify() 這個方法,方便咱們把原來的異步回調方法改爲支持 Promise 的方法,接下來,想繼續 .then().then().then() 搞隊列,仍是 await 就看實際須要了。node

咱們看下範例,讓讀取目錄文件狀態的 fs.stat 支持 Promise:jquery

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

怎麼樣,很簡單吧?按照文檔的說法,只要符合 Node.js 的回調風格,全部函數均可以這樣轉換。也就是說,只要知足下面兩個條件,不管是否是原生方法,均可以:git

  1. 最後一個參數是回調函數github

  2. 回調函數的參數爲 (err, result),前面是可能的錯誤,後面是正常的結果小程序

結合 Await/Async 使用

一樣是上面的例子,若是想要結合 Await/Async,能夠這樣使用:segmentfault

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);
async function readStats(dir) {
  try {
    let stats = await stat(dir);
    // Do something with `stats`
  } catch (err) { // Handle the error.
    console.log(err);
  }
}
readStats('.');

自定義 Promise 化處理函數

那若是現有的使用回調的函數不符合這個風格,還能用 util.promisify() 麼?答案也是確定的。咱們只要給函數增長一個屬性 util.promisify.custom,指定一個函數做爲 Promise 化處理函數,便可。請看下面的代碼:api

const util = require('util');

// 這就是要處理的使用回調的函數
function doSomething(foo, callback) { 
  // ...
}

// 給它增長一個方法,用來在 Promise 化時調用
doSomething[util.promisify.custom] = function(foo) { 
  // 自定義生成 Promise 的邏輯
  return getPromiseSomehow(); 
};

const promisified = util.promisify(doSomething);
console.log(promisified === doSomething[util.promisify.custom]);
// prints 'true'

如此一來,任什麼時候候咱們對目標函數 doSomething 進行 Promise 化處理,都會獲得以前定義的函數。運行它,就會按照咱們設計的特定邏輯返回 Promise 實例。

咱們就能夠升級之前全部的異步回調函數了。


Promise 介紹

由於種種歷史緣由,JS 當中有大量異步函數。這些異步函數,大多要依賴回調進行處理(這裏我以爲把事件偵聽算做回調也是合理的),可是回調嵌套層次一多,就會造成所謂的「回調陷阱」,讓開發者苦不堪言。

回調陷阱

爲了解決這個問題,開發社區通過摸索,總結出來一套名爲 Promise/A+ 的解決方案。大致上來講,這套方案經過使用 「Promise 回調實例」包裹原先的回調函數,能夠將原先複雜的嵌套展開、鋪平,從而下降開發和維護的難度和成本。

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

ES2015(ES6)裏包含了 Promise 標準,現在已經在大部分運行時裏實裝,咱們能夠放心大膽的使用它。並且,因爲 Promise 不須要新的語法元素,因此即便在不支持原生 Promise 的環境裏也可使用類庫,好比 Q 或者 Bluebird,甚至 jQuery

在小程序裏也有效喲!

ES2017 增長了 Await/Async 語法,但請注意,Await 後面必須跟 Promise 實例才能實現異步。因此,你們仍是把 Promise 的概念學好吧!

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}
f1();

例子來源於 MDN


若是你想進一步學習使用 Promise,強烈推薦個人此次分享:Promise 的 N 種用法。能夠幫助你一站式的學會使用 Promise。


PS2:剛纔看到 Node.js 已經發布 8.1 了,真快呀……新版本的 Changelog 在這裏,已修復爲主。


擴展閱讀:


同步發於 個人博客

相關文章
相關標籤/搜索