[源码共读] 第十四期笔记 : promisify

153 阅读3分钟

通用 promisify 函数

function promisify(original){
   function fn(...args){
      return new Promise((resolve, reject) => {
         args.push((err, ...values) => {
            if(err){
               return reject(err);
            }
            resolve(values);
         });
         // original.apply(this, args);
         Reflect.apply(original, this, args);
      });
   }
   return fn;
}

promisify函数是把 callback 形式转成 promise 形式。

为什么要这么写呢?

因为Node.js 天生异步,错误回调的形式书写代码。回调函数的第一个参数是错误信息。也就是错误优先。

一般的node异步方法是这样的:

 fs.readFile('./data.json', 'utf-8', function(error, data) {
     if (error) {console.log(error)}
     console.log(data)
 })

一般是最后一个参数作为回调函数,而回调函数有两个参数,第一个是error,第二个是data,错误优先。

而上面的promisify方法则是将类似readFile这种类型的方法转为promise,使其能进行链式处理回调。

示例:

如未使用promisify,则是:

const fs=require('fs');
const {resolve} = require("./webpack/webpack.config");
fs.readFile('./data.json', 'utf-8', function(error, data) {
    if (error) {console.log(error)}
    //[ '{\r\n  "name": "tom"\r\n}\r\n' ]
    console.log(data) 
})

如果使用了prmisify,则是:

const fs=require('fs');
function promisify(original){
    function fn(...args){
        return new Promise((resolve, reject) => {
            args.push((err, ...values) => {
                if(err){
                    return reject(err);
                }
                resolve(values);
            });
            // original.apply(this, args);
            Reflect.apply(original, this, args);
        });
    }
    return fn;
}
const readFileAsync=promisify(fs.readFile)
readFileAsync('./data.json','utf-8').then(res=>{
    //{"name": "tom"}
    console.log(res)   
}).catch(err=>{
    console.log(err)
})

promisify 方法传入一个需要promise化的函数function,而这个函数的最后一个参数是一个错误优先的回调函数,然后

promisify方法会返回一个函数functionAsync,functionAsync的返回值一个Promise的实例对象,使其可以进行链式处理回调,然后functionAsync的参数则是原本function的除了回调之外的参数,因为在Promise内部会执行这个

args.push((err, ...values) => {
    if(err){
        return reject(err);
    }
    resolve(values);
});

就是加入一个回调函数到参数数组中,这个回调会把原来function中的err和data作为reject和resolve的传递值传出到then方法和catch方法。然后通过Reflect.apply(original, this, args);执行原本的函数,这样就自动化完成了Promise化。

这样说有点绕,

例子解释:

例子:

const fs=require('fs');
fs.readFile('./data.json', 'utf-8', function(error, data) {
    if (error) {console.log(error)}
    //[ '{\r\n  "name": "tom"\r\n}\r\n' ]
    console.log(data) 
})
function promisify(original){
   function fn(...args){
      return new Promise((resolve, reject) => {
         args.push((err, ...values) => {
            if(err){
               return reject(err);
            }
            resolve(values);
         });
         // original.apply(this, args);
         Reflect.apply(original, this, args);
      });
   }
   return fn;
}
const readFileAsync=promisify(fs.readFile)
const readFilePrmise=readFileAsync('./data.json','utf-8')
readFilePromise.then(res=>{
   //{"name": "tom"}
   console.log(res)
}).catch(err=>{
   console.log(err)
})

还是用这个例子,

fs.readFile方法接收3个参数,我这里的例子 第一个是 './data.json',第二个参数'utf-8',第三个参数是一个回调函数,回调函数中的参数是error和data。

const readFileAsync=promisify(fs.readFile)

这里我把fs.readFile作为参数传入到了promisify函数里,得到一个fn,然后将这个fn赋值给readFileAsync,

即是

const readFileAsync=fn(...args){
      return new Promise((resolve, reject) => {
         args.push((err, ...values) => {
            if(err){
               return reject(err);
            }
            resolve(values);
         });
         // original.apply(this, args);
         Reflect.apply(original, this, args);
      });

然后我调用了这个readFileAsync函数

readFileAsync('./data.json','utf-8'),并把原来函数fs.readFile应该传的除回调以外的两个参数 './data.json'和'utf-8'传给readFileAsync

然后内部就会执行

args.push((err, ...values) => {
    if(err){
        return reject(err);
    }
    resolve(values);
});

将一个回调放到参数数组的末尾,也就是参数数组中有了三个参数,分别是

['./data.json','utf-8',(err, ...values) => {
if(err){
return reject(err);
}
resolve(values);
}]

然后再调用了Reflect.apply(original, this, args);  这个方法,可以看作是执行了原来的函数 fs.readFile

等效于这个语句

  fs.readFile('./data.json','utf-8',(err, ...values) => {
    if(err){
        return reject(err);
    }
    resolve(values);
})

最终执行了fs.readFile方法,并把回调函数的err,和data作为reject和resolve的参数传递出去,

然后,就可以在then方法中,拿到data,在catch方法中拿到err。

readFilePromise.then(res=>{
   //{"name": "tom"}
   console.log(res)
}).catch(err=>{
   console.log(err)
})

这样就完成了promise化,这就是promisify方法的运行逻辑。