Javascript異步演化史
前幾天看了一個(gè)Javascript異步演化史河狐,談到從callback到Promise再到Async/Await的歷程盼忌,很有趣赘方。大家有興趣的話可以去看一下原文铛楣,或是直接百度搜翻譯。
我這里默認(rèn)大家都了解了這段歷史明肮,只簡(jiǎn)單介紹一下javascript異步操作里的一個(gè)小知識(shí)點(diǎn)--promisify菱农。
從callback到promise
說(shuō)起回調(diào)(callback),那可以說(shuō)是JS最基礎(chǔ)的異步調(diào)用方式柿估,是JS為解決阻塞請(qǐng)求而量身定制出的一種設(shè)計(jì)模式循未,在JS或是說(shuō)前端大潮中有著舉足輕重的影響。但回調(diào)本身卻有幾個(gè)重大的缺陷:
- 回調(diào)嵌套秫舌,俗稱回調(diào)地獄
- 不能return的妖,反直覺
- 異常捕獲很難看,還需要再嵌套error函數(shù)
所以舅巷,現(xiàn)代Javascript的api設(shè)計(jì)早已經(jīng)轉(zhuǎn)向Promise-Based Method羔味,許多老舊代碼庫(kù)重構(gòu)后會(huì)添加.promise()
方法支持返回一個(gè)new Promise。
不過(guò)钠右,我自己在開發(fā)的項(xiàng)目中還是能遇到許多遺產(chǎn)代碼赋元,依舊是各種callback,有什么方法能兼容一下ES6的Promise呢飒房?這就是我今天會(huì)提到的promisify搁凸。
以下是一個(gè)NodeJS讀取文件的片段,典型的回調(diào)案例狠毯,讀取文件后的業(yè)務(wù)會(huì)被包裹在cb
函數(shù)里护糖。我希望promisify
后的函數(shù)能實(shí)現(xiàn)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的構(gòu)造函數(shù):
new Promise( function executor(resolve, reject) { ... } );
構(gòu)參是一個(gè)executor函數(shù)嫡良,并傳入resolve
和reject
兩個(gè)判定函數(shù),分別用于判斷promise是否成功献酗。只要resolve
寝受、reject
其一被執(zhí)行,Promise異步調(diào)用就立即結(jié)束(另一個(gè)判定將被忽略)罕偎。若resolve
被調(diào)用很澄,則then
里返回data;若reject
被調(diào)用颜及,則catch
里拋出error甩苛。
OK,稍微改造一下上述代碼片段(改造如下)俏站,就達(dá)到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需求可能在項(xiàng)目中廣泛存在,每次調(diào)用new Promise構(gòu)造函數(shù)總顯得不是那么優(yōu)雅乾翔。
動(dòng)一下腦筋爱葵,其實(shí)可以實(shí)現(xiàn)一個(gè)通用的工廠方法施戴。我用閉包寫了一個(gè)乞丐版的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)
} )
}
}
工廠方法調(diào)用如下所示萌丈,是不是方便多了赞哗?
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)
}
第三方庫(kù)
事實(shí)上NodeJS自己的util庫(kù)里就已經(jīng)實(shí)現(xiàn)了promisify
const {promisify} = require('util');
let readFilePromisify = promisify(fs.readFile);
前端的話可以import藍(lán)鳥(bluebird)的promisify。聽說(shuō)該庫(kù)的Promise性能是原生的三倍辆雾。
import {promisify} from 'bluebird';
let readFilePromisify = promisify(fs.readFile);
小結(jié)
今天講解了一下如何實(shí)現(xiàn)一個(gè)promisify
將callback轉(zhuǎn)成Promise肪笋,內(nèi)容很簡(jiǎn)單,也沒有太多新意度迂,更多的細(xì)節(jié)就不展開了藤乙。
最近看到某廠還在維護(hù)遺產(chǎn)代碼,寫的是ES5惭墓,用到了jQuery和google closure library坛梁。我很驚訝的是,雖然jQuery和closure中已經(jīng)實(shí)現(xiàn)了Promise庫(kù)腊凶,但是大家還是寫著各種回調(diào)地獄划咐。似乎沒有一個(gè)人想過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的promisify轉(zhuǎn)向串行風(fēng);無(wú)論前端還是后端钧萍,日復(fù)一日寫著相同的代碼褐缠,一成不變。
有時(shí)候落后并不見得是手上的工具风瘦,更可能是你的思考方式队魏。