let和const、var
JavaScript非同步演化史
前幾天看了一個Javascript 非同步演化史,談到從 callback 到 Promise 再到 Async/Await 的歷程,很有趣。大家有興趣的話可以去看一下原文,或是直接百度搜翻譯。
我這裡預設大家都瞭解了這段歷史,只簡單介紹一下JavaScript非同步操作裡的一個小知識點--promisify。
從 callback 到 promise
說起回撥(callback),那可以說是js最基礎的非同步呼叫方式,是js為解決阻塞請求而量身定製出的一種設計模式,在 JS 或是說前端大潮中有著舉足輕重的影響。但回撥本身卻有幾個重大的缺陷:
- 回撥巢狀,俗稱回撥地獄
- 不能 return,反直覺
- 異常捕獲很難看,還需要再巢狀 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 轉向序列風;無論前端還是後端,日復一日寫著相同的程式碼,一成不變。
有時候落後並不見得是手上的工具,更可能是你的思考方式。