Callback 与 Promise 间的桥梁 —— promisify

作者:晃晃 本文原创,转载请注明作者及出处

Promise 自问世以来,得到了大量的应用,简直是 javascript 中的神器。它很好地解决了异步方法的回调地狱、提供了我们在异步方法中使用 return 的能力,并将 callback 的调用纳入了自己的管理,而不是交给异步函数后我们就无能为力了(经常有 callback 被莫名调用两次而导致程序出错)。

今天要介绍的是 Promisify,就是回调函数与 Promise 间的桥梁。

1. promisify 介绍

什么是 promisify 呢?顾名思义,就是“promise 化”,将一个不是promise的方法变成 promise 。举个例子:

// 原有的callback调用
fs.readFile('test.js', function(err, data) {
    if (!err) {
        console.log(data);
    } else {
        console.log(err);
    }
});

// promisify后
var readFileAsync = promisify(fs.readFile);
readFileAsync('test.js').then(data => {
    console.log(data);
}, err => {
    console.log(err);
});

这两个方法效果上是等价的,但是从掌控性来说的话,我更喜欢后面的写法。

那么什么样的方法可以通过 promisify 变成 promise 呢?这里就需要介绍一个名词,nodeCallback。什么样的 callback 叫 nodeCallback ?

nodeCallback 有两个条件:1. 回调函数在主函数中的参数位置必须是最后一个;2. 回调函数参数中的第一个参数必须是 error 。举个例子:

  1. 回调函数在主函数中的参数位置 ```javascript // 正确 function main(a, b, c, callback) {

}

// 错误 function main(callback, a, b, c) {

} 2. 回调函数参数中的第一个参数必须是 errorjavascript // 正确 function callback(error, result1, result2) {

}

// 错误 function callback(result1, result2, error) {

} ``` 这样,通过 nodeCallback ,我们定义了一个能被 promisify 的函数的格式,即,满足 nodeCallback 形式的方法,我们可以通过 promisify 来让它变成一个返回 promise 的方法。

2. promisify 的实现

下面我们来根据上述条件来手动实现一个 promisify 。

首先 promisify 需要返回一个 function ,并且这个 function 要返回一个 promise

var promisify = (func) => {
    return function() {
        var ctx = this;
        return new Promise(resolve => {
            return func.call(ctx, ...arguments);
        })
    }
}

其次,原 func 的最后一个参数是 callback

var promisify = (func) => {
    return function() {
        var ctx = this;
        return new Promise(resolve => {
            return func.call(ctx, ...arguments, function() {
                resolve(arguments);
            });
        })
    }
}

然后,回调函数中的第一个参数是 error 标记

var promisify = (func) => {
    return function() {
        var ctx = this;
        return new Promise((resolve, reject) => {
            return func.call(ctx, ...arguments, function() {
                var args = Array.prototype.map.call(arguments, item => item);
                var err = args.shift();
                if (err) {
                    reject(err);
                } else {
                    resolve(args);
                }
            });
        })
    }
}

最后,做一些优化,比如 this 作用域的自定义、回参只有一个时不返回数组

var promisify = (func, ctx) => {
    // 返回一个新的function
    return function() {
        // 初始化this作用域
        var ctx = ctx || this;
        // 新方法返回的promise
        return new Promise((resolve, reject) => {
            // 调用原来的非promise方法func,绑定作用域,传参,以及callback(callback为func的最后一个参数)
            func.call(ctx, ...arguments, function() {
                // 将回调函数中的的第一个参数error单独取出
                var args = Array.prototype.map.call(arguments, item => item);
                var err = args.shift();
                // 判断是否有error
                if (err) {
                    reject(err)
                } else {
                    // 没有error则将后续参数resolve出来
                    args = args.length > 1 ? args : args[0];
                    resolve(args);
                }
            });
        })
    };
};

测试

// nodeCallback方法func1
var func1 = function(a, b, c, callback) {
    callback(null, a+b+c);
}
// promise化后的func2
var func2 = promisify(func1);
// 调用后输出6
func1(1, 2, 3, (err, reuslt) => {
    if (!err) {
        console.log(result); //输出6
    }
})
func2(1, 2, 3).then(console.log); //输出6

以上便是 promisify 的介绍和实现,事实上有很多用 callback 来实现异步的第三方库提供的方法都是按照 nodeCallback 格式的,所以它们都可以通过 promisify 来让它变成 promise ,在遇到这些方法的时候就可以更灵活地使用啦。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏nummy

ECMAScript 6 特性ECMAScript 6 特性

ECMAScript 6,也被称做ECMAScript 2015,是ECMAScript标准的下一个版本。这个标准预计将于2015年6月被正式批准。ES6是这门...

571
来自专栏码农阿宇

关于C#委托的一些学习笔记

1.什么是委托就是把方法作为参数传给另一个方法。委托说指向的函数,必须和函数具有相同的签名(返回值和参数类型) Public delegate void D...

2557
来自专栏全沾开发(huā)

一个有味道的函数

一个有味道的函数 最近想到了一个自认为很有意思的面试题 如何实现一个compose函数。 函数接收数个参数,参数均为Function...

34212
来自专栏偏前端工程师的驿站

Velocity魔法堂系列三:模板与宿主环境通信

一、前言                             Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板...

1819
来自专栏CSDN技术头条

Redis为什么这么快?一文深入了解Redis!

来源:http://www.cnblogs.com/kismetv/p/8654978.html

753
来自专栏老马说编程

计算机程序的思维逻辑 (9) - 强大的循环

循环 上节我们介绍了流程控制中的条件执行,根据具体条件不同执行不同操作。本节我们介绍流程控制中的循环,所谓循环就是多次重复执行某些类似的操作,这个操作一般不是...

1918
来自专栏nimomeng的自我进阶

《Objective C编程》笔记

1.为什么main()总是返回0?可以将main()的返回结果视为“粗欧文报告”,这样0就是好消息,没有错误就是成功。 2.消息:消息发送(指令)必须写在一对...

723
来自专栏贾鹏辉的技术专栏@CrazyCodeBoy

ES6、ES7、ES8学习指南

ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前JavaScript使用的ECMAScript版本为ECMAScript-...

814
来自专栏lulianqi

理解委托类型

delegate 是一种可用于封装命名或匿名方法的引用类型。(并不是所有的引用类型都是class)

500
来自专栏漫漫前端路

从 Vue typings 看 “this”

在 2.5.0 版本中,Vue 大大改进了类型声明系统以更好地使用默认的基于对象的 API。

742

扫码关注云+社区