1. 程式人生 > 實用技巧 >日誌工廠

日誌工廠

JavaScript非同步演化史

前幾天看了一個Javascript 非同步演化史,談到從 callback 到 Promise 再到 Async/Await 的歷程,很有趣。大家有興趣的話可以去看一下原文,或是直接百度搜翻譯。

我這裡預設大家都瞭解了這段歷史,只簡單介紹一下JavaScript非同步操作裡的一個小知識點--promisify。

從 callback 到 promise

說起回撥(callback),那可以說是js最基礎的非同步呼叫方式,是js為解決阻塞請求而量身定製出的一種設計模式,在 JS 或是說前端大潮中有著舉足輕重的影響。但回撥本身卻有幾個重大的缺陷:

  1. 回撥巢狀,俗稱回撥地獄
  2. 不能 return,反直覺
  3. 異常捕獲很難看,還需要再巢狀 error函式

所以,現代 Javascript 的 api 設計早已經轉向 Promise-Based Method,許多老舊程式碼庫重構後會新增.promise()方法支援返回一個 new Promise。

不過,我自己在開發的專案中還是能遇到許多遺產程式碼,依舊是各種 callback,有什麼方法能相容一下 ES6 的 Promise 呢?這就是我今天會提到的promisify

以下是一個 NodeJS 讀取檔案的片段,典型的回撥案例,讀取檔案後的業務會被包裹在cb函式裡。我希望promisify後的函式能實現 then 到 data、catch 到 err 的序列操作。

const fs = require('fs');
fs.readFile('file.txt', 'utf8', function cb(err, data) {
  if (err) {
    console.error(err); 
    return;
  }
  console.log( data);
});

回憶一下Promise的建構函式:

new Promise( function executor(resolve, reject) { ... } );

構參是一個 executor 函式,並傳入resolve和reject兩個判定函式,分別用於判斷 promise 是否成功。只要resolve、reject其一被執行,Promise 非同步呼叫就立即結束(另一個判定將被忽略)。若resolve被呼叫,則then裡返回 data;若reject被呼叫,則catch裡丟擲 error。

OK,稍微改造一下上述程式碼片段(改造如下),就達到promisify的效果了。

new Promise( (resolve, reject) => {
    fs.readFile(fileName, 'utf8', (err, data) => err? reject(err) : resolve(data));
  }).then( (data) => {
    console.log(data);
  }).catch( (err) => {
    console.error(err);
  });

通用 promisify 方法

promisify 需求可能在專案中廣泛存在,每次呼叫 new Promise 建構函式總顯得不是那麼優雅。 動一下腦筋,其實可以實現一個通用的工廠方法。我用閉包寫了一個乞丐版的 promisify。

function promisify (originFn) {

  return function(...args) {
    return new Promise( (resolve, reject) => {

      let cb = (err, data) => err ? reject(err) : resolve(data);

      originFn.call(this, ...args, cb)
    } )
  }
}

工廠方法呼叫如下所示,是不是方便多了?

let readFilePromisify = promisify(fs.readFile);

readFilePromisify(fileName, 'utf8')
  .then( (data) => {
    console.log(data);
  }).catch( (err) => {
    console.error(err);
  });

還可以用於 async/await

try {
  let data = await readFilePromisify(fileName, 'utf8');
  console.log(data);
} catch ( err ) {
  console.error(err)
}

資源搜尋網站大全 https://www.renrenfan.com.cn

第三方庫

事實上 NodeJS 自己的 util 庫裡就已經實現了 promisify

const {promisify} = require('util');
let readFilePromisify = promisify(fs.readFile);

前端的話可以 import 藍鳥(bluebird)的 promisify。聽說該庫的 Promise 效能是原生的三倍。

import {promisify} from 'bluebird';
let readFilePromisify = promisify(fs.readFile);

小結

今天講解了一下如何實現一個promisify將 callback 轉成 Promise,內容很簡單,也沒有太多新意,更多的細節就不展開了。

最近看到某廠還在維護遺產程式碼,寫的是 ES5,用到了 jQuery 和 google closure library。我很驚訝的是,雖然 jQuery 和 closure 中已經實現了 Promise 庫,但是大家還是寫著各種回撥地獄。似乎沒有一個人想過實現一個簡單的 promisify 轉向序列風;無論前端還是後端,日復一日寫著相同的程式碼,一成不變。

有時候落後並不見得是手上的工具,更可能是你的思考方式。