NodeJS中利用Promise来封装异步函数


Posted in NodeJs onFebruary 25, 2015

 在写Node.js的过程中,连续的IO操作可能会导致“金字塔噩梦”,回调函数的多重嵌套让代码变的难以维护,利用CommonJs的Promise来封装异步函数,使用统一的链式API来摆脱多重回调的噩梦。

Node.js提供的非阻塞IO模型允许我们利用回调函数的方式处理IO操作,但是当需要连续的IO操作时,你的回调函数会多重嵌套,代码很不美观,而且不易维护,而且可能会有许多错误处理的重复代码,也就是所谓的“Pyramid of Doom”。

step1(function (value1) {

    step2(value1, function(value2) {

        step3(value2, function(value3) {

            step4(value3, function(value4) {

                // Do something with value4

            });

        });

    });

});

这其实就是Node.js的Control flow的问题,对于这个问题,解决方案都许多,比如利用async,或者eventProxy等,不过本文的主题是利用CommonJs规范中对Promise来解决这个问题。

什么是Promise?

CommonJs的Promise规范有许多种,我们一般讨论的是Promise/A+规范,它定义了Promise的基本行为。

Promise是一个对象,它通常代表一个在未来可能完成的异步操作。这个操作可能成功也可能失败,所以一个Promise对象一般有3个状态:Pending,Fulfilled,Rejected。分别代表未完成、成功完成和操作失败。一旦Promise对象的状态从Pending变成Fulfilled或者Rejected任意一个,它的状态都没有办法再被改变。

一个Promise对象通常会有一个then方法,这个方法让我们可以去操作未来可能成功后返回的值或者是失败的原因。这个then方法是这样子的:

promise.then(onFulfilled, onRejected)

显而易见的是,then方法接受两个参数,它们通常是两个函数,一个是用来处理操作成功后的结果的,另一个是用来处理操作失败后的原因的,这两个函数的第一个参数分别是成功后的结果和失败的原因。如果传给then方法的不是一个函数,那么这个参数会被忽略。

then方法的返回值是一个Promise对象,这一个特点允许我们链式调用then来达到控制流程的效果。这里有许多细节上的问题,比如值的传递或者错误处理等。Promise的规范是这样定义的:

onFulfilled或者onRejected函数的返回值不是Promise对象,则该值将会作为下一个then方法中onFulfilled的第一个参数,如果返回值是一个Promise对象,怎么then方法的返回值就是该Promise对象
onFulfilled或者onRejected函数中如果有异常抛出,则该then方法的返回的Promise对象状态转为Rejected,如果该Promise对象调用then,则Error对象会作为onRejected函数的第一个参数
如果Promise状态变为Fulfilled而在then方法中没有提供onFulfilled函数,则then方法返回的Promise对象状态变为Fulfilled且成功的结果为上一个Promise的结果,Rejected同理。

补充一句,onFulfilled和onRejected都是异步执行的。

规范的实现:q

上面讲的是Promise的规范,而我们需要的是它的实现,q是一个对Promise/A+有着较好实现规范的库。

首先我们需要创建一个Promise对象,关于Promise对象创建的规范在Promise/B中,这里不做详细的解释,直接上代码。

    function(flag){

        var defer = q.defer();

        fs.readFile("a.txt", function(err, data){

        if(err) defer.reject(err);

            else defer.resolve(data);

            });

            return defer.promise;

    }

多数Promise的实现在Promise的创建上大同小异,通过创建一个具有promise属性的defer对象,如果成功获取到值则调用defer.resolve(value),如果失败,则调用defer.reject(reason),最后返回defer的promise属性即可。这个过程可以理解为调用defer.resolve将Promise的状态变成Fulfilled,调用defer.reject将Promise的状态变成Rejected。

在面对一系列连续的异步方法时,怎么利用Promise写出漂亮的代码呢?看下下面的例子。

    promise0.then(function(result){

        // dosomething

        return result;

    }).then(function(result) {

        // dosomething

        return promise1;    

    }).then(function(result) {

        // dosomething

    }).catch(function(ex) {

        console.log(ex);

    }).finally(function(){

        console.log("final");

    });

在上面的代码中,then方法只接受OnFulfilled,而catch方法实际上就是then(null, OnRejected),这样的话只要一系列异步方法只要始终是成功返回值的,那么代码就会瀑布式的向下运行,如果其中任意一个异步方法失败或者发生异常,那么根据CommonJs的Promise规范,将执行catch中的function。q还提供了finally方法,从字面上也很好理解,就是不论resolve还是reject,最终都会执行finally中的function。

看上去似乎不错,代码更以维护且美观了,那么如果希望并发呢?

     q.all([promise0, promise1, promise2]).spread(function(val0, val1, val2){

                    console.log(arguments);

                }).then(function(){

                    console.log("done");

                }).catch(function(err){

                    console.log(err);

                });

q也为并发提供了api,调用all方法并传递一个Promise数组即可继续使用then的链式风格。还有像q.nfbind等可以将Node.js的原生API转化成Promise来统一代码格式也是挺好的。更多api在这里就不一一详述了。

结论

本文主要介绍通过使用Promise来解决Node.js控制流问题,但Promise也可同样应用于前端,EMCAScript6已经提供了原生的API支持。需要指出的是Promise并不是唯一的解决方案,async也是一个很好的选择,并且提供更友好的并发控制API,不过我觉得Promise在封装具有异步方法的函数时更具优势。

好了,本文就先到这里了,希望对大家能够有所帮助。

NodeJs 相关文章推荐
nodejs批量修改文件编码格式
Jan 22 NodeJs
nodejs URL模块操作URL相关方法介绍
Mar 03 NodeJs
Nodejs实战心得之eventproxy模块控制并发
Oct 27 NodeJs
详解nodejs 文本操作模块-fs模块(四)
Dec 22 NodeJs
用Nodejs搭建服务器访问html、css、JS等静态资源文件
Apr 28 NodeJs
nodejs中sleep功能实现暂停几秒的方法
Jul 12 NodeJs
nodejs实现解析xml字符串为对象的方法示例
Mar 14 NodeJs
详解从NodeJS搭建中间层再谈前后端分离
Nov 13 NodeJs
nodejs 使用http进行post或get请求的实例(携带cookie)
Jan 03 NodeJs
nodejs npm错误Error:UNKNOWN:unknown error,mkdir 'D:\Develop\nodejs\node_global'at Error
Mar 02 NodeJs
Nodejs使用archiver-zip-encrypted库加密压缩文件时报错(解决方案)
Nov 18 NodeJs
NodeJS实现一个聊天室功能
Nov 25 NodeJs
NodeJS Web应用监听sock文件实例
Feb 18 #NodeJs
NodeJS使用jQuery选择器操作DOM
Feb 13 #NodeJs
NodeJs基本语法和类型
Feb 13 #NodeJs
nodejs事件的监听与触发的理解分析
Feb 12 #NodeJs
nodejs 整合kindEditor实现图片上传
Feb 03 #NodeJs
NodeJS学习笔记之Connect中间件应用实例
Jan 27 #NodeJs
NodeJS学习笔记之Connect中间件模块(二)
Jan 27 #NodeJs
You might like
探讨:web上存漏洞及原理分析、防范方法
2013/06/29 PHP
服务器变量 $_SERVER 的深入解析
2013/07/02 PHP
php压缩和解压缩字符串的方法
2015/03/14 PHP
详解js异步文件加载器
2016/01/24 PHP
PHP实现163邮箱自动发送邮件
2016/03/29 PHP
laravel http 自定义公共验证和响应的方法
2019/09/29 PHP
可拖动窗口,附带鼠标控制渐变透明,开启关闭功能
2006/06/26 Javascript
JQquery的一些使用心得分享
2012/08/01 Javascript
showModelDialog弹出文件下载窗口的使用示例
2013/11/19 Javascript
使用iojs的jsdom库实现同步系统时间
2015/04/20 Javascript
异步安全加载javascript文件的方法
2015/07/21 Javascript
谈一谈javascript闭包
2016/01/28 Javascript
JS实现复制功能
2017/03/01 Javascript
vue-devtools的安装步骤
2018/04/23 Javascript
Vue.js中使用iView日期选择器并设置开始时间结束时间校验功能
2018/08/12 Javascript
vue: WebStorm设置快速编译运行的方法
2018/10/18 Javascript
Vue Autocomplete 自动完成功能简单示例
2019/05/25 Javascript
JS数组的常用10种方法详解
2020/05/08 Javascript
[35:29]Secret vs VG 2018国际邀请赛淘汰赛BO3 第三场 8.23
2018/08/24 DOTA
使用python搭建Django应用程序步骤及版本冲突问题解决
2013/11/19 Python
深入讨论Python函数的参数的默认值所引发的问题的原因
2015/03/30 Python
使用Pyrex来扩展和加速Python程序的教程
2015/04/13 Python
探究Python多进程编程下线程之间变量的共享问题
2015/05/05 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
2017/12/14 Python
python3的数据类型及数据类型转换实例详解
2019/08/20 Python
Python中文分词库jieba,pkusegwg性能准确度比较
2020/02/11 Python
深入解析HTML5 Canvas控制图形矩阵变换的方法
2016/03/24 HTML / CSS
食堂个人先进事迹
2014/01/22 职场文书
会计核算科岗位职责
2014/03/19 职场文书
人大代表选举标语
2014/10/07 职场文书
2014大学班主任工作总结
2014/11/08 职场文书
2014年防汛工作总结
2014/12/08 职场文书
导师对论文的学术评语
2015/01/04 职场文书
谢师宴家长答谢词
2015/09/30 职场文书
2016年入党心得体会范文
2016/01/23 职场文书
详解Python函数print用法
2021/06/18 Python