系列文章:javascript
以前閱讀的 npm 模塊都來源於 awesome-micro-npm-packages 這個項目,不過瀏覽了一些以後,發現好多都不太適合拿來作源碼學習。若是讀者有推薦的適合的模塊,歡迎在評論區指出 😊html
今天閱讀的模塊是 pify,經過它能夠將不少採用 callback 方式進行調用的函數變成 Promise 調用,甚至採用 async/await 語法進行異步調用,從而能夠在不修改調用函數的狀況下避免回調地獄,也可讓代碼具備更好的可讀性,當前的版本是 4.0.0,周下載量約爲 750 萬。java
以 Node.js 中異步讀取文件爲例,經常使用的方法之一就是 fs.readFile(path, encoding, callback)
,這種經過回調函數進行異步操做的方式在之前的代碼中十分常見 ,也是無可奈何。可是當現在擁有了 Promise 以後,這樣寫就顯得十分麻煩,也不易於維護,因此能夠經過 pify
這個模塊將他們 Promise 化(即 Promisify)。node
const fs = require('fs');
const pify = require('pify');
// 將 fs.readFile 變成 Promise 調用
pify(fs.readFile)('package.json', 'utf8').then(data => {
console.log(JSON.parse(data).name);
// => 'pify'
});
// 經過 Promise 化函數,使用 async/await 語法
(async function(){
const data = await pify(fs.readFile)('package.json', 'utf-8');
console.log(JSON.parse(data));
// => 'pify'
})();
複製代碼
除了直接對一個函數進行 Promise 化外,還能夠對一整個模塊中的每個函數進行 Promise 化:git
const fs = require('fs');
const pify = require('pify');
// 將 fs 模塊 Promise 化
pify(fs).readFile('package.json', 'utf8').then(data => {
console.log(JSON.parse(data).name);
// => 'pify'
});
複製代碼
// 源碼 6-1
module.exports = (input) => {
let ret;
if (typeof input === 'function') {
ret = (...args) => processFn(input)(...args);
}
return ret;
}
複製代碼
pify
主函數入口十分簡單,若是傳入的參數爲函數,則通過 processFn
處理後做爲結果返回,這裏兩個 ...args
雖然看起來同樣,但其實是 ES6新增的不一樣語法:github
第一個 ...args
用法叫作函數 rest 參數,能夠用來獲取函數的多餘參數。它不一樣於 arguments
是一個類數組的類型,而是一個數組的實例:npm
function foo(name, ...rest) {
console.log(rest, rest instanceof Array);
}
foo('Elvin', 'likes', 'JavaScript');
// => [ 'likes', 'JavaScript' ], true
複製代碼
第二個 ...args
的用法叫作擴展運算符(spread),相似於 rest 參數的逆運算,將一個數組進行展開:json
const x = [1, 2, 3];
const y = [...x, 4];
console.log(...x);
// => 1 2 3
console.log(y);
// =>[ 1, 2, 3, 4 ]
複製代碼
這裏實際上沒有必要進行一層包裹,能夠直接返回
processFn
處理的函數,即變成ret = processFn(input)
,我也根據這個想法提出了 pify - PR#65。segmentfault
接下來看一看 processFn
這個函數的具體實現。這個函數也十分簡單,主要作了四件事情:api
Promise.reject()
方法拋出異常;假如無錯誤,則調用 Promise.resolve()
返回正常結果。args
經過 push 方法追加咱們剛剛構造的 callback 函數,從而造成完整的參數。fn.apply(this, args)
調用原函數。// 源碼 6-2
const processFn = (fn) => function (...args) {
return new Promise((resolve, reject) => {
args.push((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
fn.apply(this, args);
});
};
複製代碼
對象的 Promise 化其實就是遍歷對象的每個屬性,若是屬性類型爲函數的話,那麼就用上節所說的 processFn
進行處理;若是屬性類型不爲函數的話,則直接返回:
// 源碼 6-3
module.exports = (input) => {
for (const key in input) {
const property = input[key];
ret[key] = typeof property === 'function' ? processFn(property) : property;
}
}
複製代碼
今天閱讀的 pify 模塊的代碼其實不難,可是它的的確確解決了開發過程當中的痛點,因此它能在 Github -pify 上得到 1000+ 的贊,在 npm 上每週的下載量高達 750 萬。
另外從 Node.js 8.0 起,就內置了 util.promisify(fn)
方法,能夠實現部分 pify 的功能,官方文檔能夠參考 Node.js - util.promisify,關於二者的區別能夠參考 How does this differ from util.promisfy,主要爲兩點:
pify
支持 Node.js 6.0 及以上版本, util.promisify(fn)
只支持 Node.js 8.0 及以上版本。pify
支持對整個模塊 Promise 化, util.promisify(fn)
只支持對單個函數的 Promise 化。關於我:畢業於華科,工做在騰訊,elvin 的博客 歡迎來訪 ^_^