之前阅读的 npm 模块都来源于 https://github.com/parro-it/awesome-micro-npm-packages 这个项目, 不过浏览了一些之后, 发现好多都不太适合拿来做源码学习. 如果读者有推荐的适合的模块, 欢迎在评论区指出
一句话介绍
今天阅读的模块是 https://www.npmjs.com/package/pify , 通过它可以将很多采用 callback 方式进行调用的函数变成 Promise 调用, 甚至采用 async/await 语法进行异步调用, 从而可以在不修改调用函数的情况下避免回调地狱, 也可以让代码具有更好的可读性, 当前的版本是 4.0.0, 周下载量约为 750 万.
用法
以 Node.js 中异步读取文件为例, 常用的方法之一就是 fs.readFile(path, encoding, callback), 这种通过回调函数进行异步操作的方式在以前的代码中十分常见 , 也是迫不得已. 但是当如今拥有了 Promise 之后, 这样写就显得十分麻烦, 也不易于维护, 所以可以通过 pify 这个模块将他们 Promise 化 (即 Promisify).
- 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 化:
- 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 新增的不同语法:
第一个 ...args 用法叫做函数 rest 参数, 可以用来获取函数的多余参数. 它不同于 arguments 是一个类数组的类型, 而是一个数组的实例:
- function foo(name, ...rest) {
- console.log(rest, rest instanceof Array);
- }
- foo('Elvin', 'likes', 'JavaScript');
- // => [ 'likes', 'JavaScript' ], true
复制代码
第二个 ...args 的用法叫做扩展运算符 (spread), 类似于 rest 参数的逆运算, 将一个数组进行展开:
- 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 https://github.com/sindresorhus/pify/pull/65 .
接下来看一看 processFn 这个函数的具体实现. 这个函数也十分简单, 主要做了四件事情:
构造一个 Promise 并将其作为函数的返回值.
构造一个 callback 函数, 在这个函数中, 假如有错误, 则调用 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 化
对象的 Promise 化其实就是遍历对象的每一个属性, 如果属性类型为函数的话, 那么就用上节所说的 processFn 进行处理; 如果属性类型不为函数的话, 则直接返回:
- // 源码 6-3
- module.exports = (input) => {
- for (const key in input) {
- const property = input[key];
- ret[key] = typeof property === 'function' ? processFn(property) : property;
- }
- }
复制代码
来源: https://juejin.im/post/5b8e1813e51d45578b0ab947