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 npm package.json中文文档
Sep 04 NodeJs
PHP和NodeJs开发的应用如何共用Session
Apr 16 NodeJs
你一定会收藏的Nodejs代码片段
Feb 04 NodeJs
NodeJs的优势和适合开发的程序
Aug 14 NodeJs
nodejs和php实现图片访问实时处理
Jan 05 NodeJs
nodeJs链接Mysql做增删改查的简单操作
Feb 04 NodeJs
mac下的nodejs环境安装的步骤
May 24 NodeJs
详解nodejs异步I/O和事件循环
Jun 07 NodeJs
nodejs动态创建二维码的方法
Aug 12 NodeJs
nodeJs实现基于连接池连接mysql的方法示例
Feb 10 NodeJs
NodeJS 实现多语言的示例代码
Sep 11 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
PHP中显示格式化的用户输入
2006/10/09 PHP
PHP中一些可以替代正则表达式函数的字符串操作函数
2014/11/17 PHP
PHP 双链表(SplDoublyLinkedList)简介和使用实例
2015/05/12 PHP
php获取字符串前几位的实例(substr返回字符串的子串用法)
2017/03/08 PHP
PHP使用gearman进行异步的邮件或短信发送操作详解
2020/02/27 PHP
splice slice区别
2006/10/09 Javascript
背景音乐每次刷新都可以自动更换
2007/02/01 Javascript
google jQuery 引用文件,jQuery 引用地址集合(jquery 1.2.6至jquery1.5.2)
2011/04/24 Javascript
使用jQueryMobile实现滑动翻页效果的方法
2015/02/04 Javascript
javascript实现下班倒计时效果的方法(可桌面通知)
2015/07/10 Javascript
jquery实现仿新浪微博带动画效果弹出层代码(可关闭、可拖动)
2015/10/12 Javascript
Web前端开发工具——bower依赖包管理工具
2016/03/29 Javascript
浅谈js和css内联外联注意事项
2016/06/30 Javascript
浅析Javascript ES6中的原生Promise
2016/08/25 Javascript
jquery事件绑定解绑机制源码解析
2016/09/19 Javascript
js实现获取鼠标当前的位置
2016/12/14 Javascript
vue loadmore组件上拉加载更多功能示例代码
2017/07/19 Javascript
微信小程序实现MUI数字输入框效果
2018/01/31 Javascript
如何利用@angular/cli V6.0直接开发PWA应用详解
2018/05/06 Javascript
小程序开发踩坑:页面窗口定位(相对于浏览器定位)(推荐)
2019/04/25 Javascript
Js生成随机数/随机字符串的方法小结【5种方法】
2020/05/27 Javascript
js实现随机点名器精简版
2020/06/29 Javascript
Vue如何循环提取对象数组中的值
2020/11/18 Vue.js
解决uWSGI的编码问题详解
2017/03/24 Python
python 执行终端/控制台命令的例子
2019/07/12 Python
python用TensorFlow做图像识别的实现
2020/04/21 Python
Jupyter打开图形界面并画出正弦函数图像实例
2020/04/24 Python
使用Keras实现简单线性回归模型操作
2020/06/12 Python
Python监听剪切板实现方法代码实例
2020/11/11 Python
突袭HTML5之Javascript API扩展5—其他扩展(应用缓存/服务端消息/桌面通知)
2013/01/31 HTML / CSS
EVE LOM英国官网:全世界最好的洁面膏
2017/10/30 全球购物
英国豪华装饰照明品牌的在线零售商:Inspyer Lighting
2019/12/10 全球购物
第一范式(1NF)、第二范式(2NF)和第三范式(3NF)之间的区别是什么?
2016/04/28 面试题
2019关于垃圾分类处理的调查报告
2019/12/26 职场文书
使用feign服务调用添加Header参数
2021/06/23 Java/Android
Python Flask搭建yolov3目标检测系统详解流程
2021/11/07 Python