天天閱讀一個 npm 模塊(6)- pify

系列文章:javascript

  1. 天天閱讀一個 npm 模塊(1)- username
  2. 天天閱讀一個 npm 模塊(2)- mem
  3. 天天閱讀一個 npm 模塊(3)- mimic-fn
  4. 天天閱讀一個 npm 模塊(4)- throttle-debounce
  5. 天天閱讀一個 npm 模塊(5)- ee-first

以前閱讀的 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'
});
複製代碼

源碼學習

函數 Promise 化

// 源碼 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#65segmentfault

接下來看一看 processFn 這個函數的具體實現。這個函數也十分簡單,主要作了四件事情:api

  1. 構造一個 Promise 並將其做爲函數的返回值。
  2. 構造一個 callback 函數,在這個函數中,假若有錯誤,則調用 Promise.reject() 方法拋出異常;假如無錯誤,則調用 Promise.resolve() 返回正常結果。
  3. 對於傳入的參數 args 經過 push 方法追加咱們剛剛構造的 callback 函數,從而造成完整的參數。
  4. 最後經過 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 化

對象的 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,主要爲兩點:

  1. pify 支持 Node.js 6.0 及以上版本, util.promisify(fn) 只支持 Node.js 8.0 及以上版本。
  2. pify 支持對整個模塊 Promise 化, util.promisify(fn) 只支持對單個函數的 Promise 化。

關於我:畢業於華科,工做在騰訊,elvin 的博客 歡迎來訪 ^_^

相關文章
相關標籤/搜索