在Node.js中使用promise摆脱回调金字塔
2015-08-12 13:27
781 查看
http://nya.io/Node-js/promise-in-nodejs-get-rid-of-callback-hell/
在开始谈论正题之前,我们先来看看下面一段代码:
是不是感觉很恐怖,随着嵌套的回调函数增加,结尾会有大量的花括号和圆括号 }); 出现。
在javascript中实现异步最简单的方式是Callback。遗憾的是,这种编程方式牺牲了控制流,同时你也不能throw new Error()并在外部捕获异常。 Promise的出现解决了这两个需求,又保持了javascript异步的优势,不同于Fiber这种多线程的实现方式,Promise只是一种编程方式的变化。而无须在底层改变。
CommonJS的规范提到了多种Promise,我们只介绍其中一种的实现q (https://github.com/kriskowal/q)
我们在这里不讲解抽象的Promise规范,这多半是实现者应该关心的,我们直接从示例入手,如果你有兴趣,可以参见Promise/A+。
q的核心是一个promise对象的then方法,他接受两个回调方法,一个promise被定义之后有3种状态,pending(过渡状态),fullfilled(完成状态),rejected(错误状态)。一个promise只能是这三种状态种的一种,而无法是他们的混合状态。
pending状态可以理解为promise还没有获得确定值,就相当于一个任务还没有完成。
fullfilled状态可以理解为完成并返回结果。这时then(onFullfilled, onRejected)的onFullfilled方法会被调用。
rejected状态可以理解为错误,并结束。返回错误。这时then(onFullfilled, onRejected)的onRejected方法会被调用。
了解了核心思想后,我们来看一个例子,在这个例子中我们先读取一个json文本文件,然后将其解析成javascript对象,最后这个对象进行修改再保存回去。 按照传统的callback写法,有如下代码:
在这个例子中,控制流被切割成多个部分(每次异步都要处理一次错误),并且 JSON.parse的错误必须在内部捕获,但却不能跑到外部。因为在异步回调中无法抛出异常。 现在当我们使用promise的时候,假设我们有个能够返回一个 promise 对象的 readFile 和 writeFile 方法。那么上面的代码就可以变成如下形式:
上面的例子中,我们首先从 readFile() 方法里获得了一个返回的 promise 对象,然后使用这个对象的 then() 方法。在这里,我们只传入了一个 onFullfilled 回调方法,根据Promise/A+的文档。then() 一定会返回一个 promise 对象,所以我们又连接了一个 then() ,由于这个 then() 是最后一个,所以我们需要在这里提供一个 onRejected() 回调方法来处理所有的错误。在第一个onFullfilled() 回调方法中,我们返回了一个 promise ,这个 promise 的处理结果将会在下一个then() 的 onFullfilled() 方法中取得。
这段代码执行的时候,当任意位置抛出异常的时候,最后一个then的 onRejected 回调会被执行。否则一切按照从上至下的顺序执行,整个控制流都十分简洁明了。
因为 then() 方法必须返回一个promise,实际上我们也可以结合同步方法返回一个已经fullfilled的promise 比如下面这个例子
上面例子中第二个then会在第一个then返回之后被执行,因为第一个then返回的时候,由于JSON.parse是同步方法,所以返回了一个值,这个值会被包装成一个fullfilled的promise.
上面例子中我们知道了如何使用promise提供的核心方法 then() 。但是对于平时使用的fs等异步的库我们要怎么才能利用promise呢。 在q的文档中介绍了q-io库,里面将常用的io方法都用promise的模式包装了一遍,在实际使用中,你可以使用那个库的方法。不过我们在这里简单的对fs进行包装,让其支持promise,这样以后遇到任何异步方法,你都可以将其转化。
首先定义改一个readFile方法,返回promise,这里利用了 Q 的 defer() 方法,创建一个deferred 对象。这个对象有连个关键的方法 resolve 和 reject() 。当resolve(value)执行之后,promise变成fullfilled状态,fullfilled的值就是value
当 reject(reason) 执行之后,promise变成了rejected状态,reason会被传递到onRejected()方法。
注意到这里面我们依然提供了一个callback,用于提供一些需要callback的场合的兼容性,我们利用 promise 对象的nodeify方法来调用这个callback,这个callback可以为undefined。
另外一点需要注意的是,一个promise状态改变之后,不能再次改变,所以,你只能调用一次reject或resolve。
有了这个API,我们便可以像前面例子里那样,使用promise来执行读取文件的操作了。其他异步回调转化成返回promise的异步方法基本上都可以参照这个模式来做。
如果你有几个异步方法,他们都返回promise,并且当这些方法都处理完之后,你才能进行下一步,Q提供了一个all()方法来帮助你消化多个promise。
在这里例子里,我们将一个promise数组传给 all() all返回一个promise,当数组里面的所有promise都为fullfilled状态时,我们的then()方法才会被调用。这时fullfilled值是一个数组,每个元素对应前面promise的fullfilled值。 当任意一个promise变成rejected状态的时候,all的promise会立即reject而不等其他的完成。
最佳的理解方法便是事件,你可以把一些nodejs的基本异步方法包装成promise,这样你就可以在整个程序的多个地方使用这些方法。并且让你的程序的异步代码看起来更整洁,更容易理解。 阅读 Q的文档 了解更多的API和方法。并在程序中使用这些方法,使你的代码更优美,逻辑更健壮。
阅读 Promise/A+ 。了解promise原理。
在开始谈论正题之前,我们先来看看下面一段代码:
1 2 3 4 5 6 7 8 9 | step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); }); |
Promise
在javascript中实现异步最简单的方式是Callback。遗憾的是,这种编程方式牺牲了控制流,同时你也不能throw new Error()并在外部捕获异常。 Promise的出现解决了这两个需求,又保持了javascript异步的优势,不同于Fiber这种多线程的实现方式,Promise只是一种编程方式的变化。而无须在底层改变。CommonJS的规范提到了多种Promise,我们只介绍其中一种的实现q (https://github.com/kriskowal/q)
我们在这里不讲解抽象的Promise规范,这多半是实现者应该关心的,我们直接从示例入手,如果你有兴趣,可以参见Promise/A+。
q的核心是一个promise对象的then方法,他接受两个回调方法,一个promise被定义之后有3种状态,pending(过渡状态),fullfilled(完成状态),rejected(错误状态)。一个promise只能是这三种状态种的一种,而无法是他们的混合状态。
pending状态可以理解为promise还没有获得确定值,就相当于一个任务还没有完成。
fullfilled状态可以理解为完成并返回结果。这时then(onFullfilled, onRejected)的onFullfilled方法会被调用。
rejected状态可以理解为错误,并结束。返回错误。这时then(onFullfilled, onRejected)的onRejected方法会被调用。
了解了核心思想后,我们来看一个例子,在这个例子中我们先读取一个json文本文件,然后将其解析成javascript对象,最后这个对象进行修改再保存回去。 按照传统的callback写法,有如下代码:
1 2 3 4 5 6 7 8 910 | fs.readFile('example.json', function(err, data){ if(err) { console.log(err): } else { try { var obj = JSON.parse(data); obj.prop = 'something new'; fs.writeFile('example.json', JSON.stringify(obj), function(error){ if(err) { console.log(error); } else { console.log('success'); } }); } catch(e) { console.log(e); } } }); |
1 2 3 4 5 6 7 8 910 | var promise = readFile(); promise .then(function(data){ // we don't need to catch error. in other words. we can throw error in this callback. var obj = JSON.parse(data); obj.prop = 'something new'; // return a promise. so we can chain the then() method. return writeFile(JSON.stringify(obj)); }) .then(function(){ console.log('success'); }, function(err){ // all error will fall down here. console.log(err); }); |
这段代码执行的时候,当任意位置抛出异常的时候,最后一个then的 onRejected 回调会被执行。否则一切按照从上至下的顺序执行,整个控制流都十分简洁明了。
因为 then() 方法必须返回一个promise,实际上我们也可以结合同步方法返回一个已经fullfilled的promise 比如下面这个例子
1 2 3 4 5 6 7 8 910 | var promise = readFile(); promise .then(function(data){ return JSON.parse(data); }) .then(function(obj){ obj.prop = 'something new'; console.log(obj); },function(err){ console.log(err); }); |
制作promise的API
上面例子中我们知道了如何使用promise提供的核心方法 then() 。但是对于平时使用的fs等异步的库我们要怎么才能利用promise呢。 在q的文档中介绍了q-io库,里面将常用的io方法都用promise的模式包装了一遍,在实际使用中,你可以使用那个库的方法。不过我们在这里简单的对fs进行包装,让其支持promise,这样以后遇到任何异步方法,你都可以将其转化。首先定义改一个readFile方法,返回promise,这里利用了 Q 的 defer() 方法,创建一个deferred 对象。这个对象有连个关键的方法 resolve 和 reject() 。当resolve(value)执行之后,promise变成fullfilled状态,fullfilled的值就是value
当 reject(reason) 执行之后,promise变成了rejected状态,reason会被传递到onRejected()方法。
1 2 3 4 5 6 7 8 910 | var Q = require('q'); function readFile(callback){ var deferred = Q.defer(); fs.readFile('example.json', function(err, data){ if(err){ deferred.reject(err); } else { deferred.resolve(data); } }); return deferred.promise.nodeify(callback); } |
另外一点需要注意的是,一个promise状态改变之后,不能再次改变,所以,你只能调用一次reject或resolve。
有了这个API,我们便可以像前面例子里那样,使用promise来执行读取文件的操作了。其他异步回调转化成返回promise的异步方法基本上都可以参照这个模式来做。
一次处理多个promise的
如果你有几个异步方法,他们都返回promise,并且当这些方法都处理完之后,你才能进行下一步,Q提供了一个all()方法来帮助你消化多个promise。1 2 3 4 5 6 7 8 910 | Q.all([ readFile('file1.json'), readFile('file2.json') ]) .then(function(dataArray){ for(var i = 0; i < dataArray.length; i++){ console.log(dataArray[i]); } }, function(err){ console.log(err); }); |
利用promise改写你的项目
最佳的理解方法便是事件,你可以把一些nodejs的基本异步方法包装成promise,这样你就可以在整个程序的多个地方使用这些方法。并且让你的程序的异步代码看起来更整洁,更容易理解。 阅读 Q的文档 了解更多的API和方法。并在程序中使用这些方法,使你的代码更优美,逻辑更健壮。阅读 Promise/A+ 。了解promise原理。
相关文章推荐
- Node.js 常见面试题
- Leetcode#19||Remove Nth Node From End of List
- node.js--Less
- [转]Nodejs开发框架Express4.x开发手记
- LeetCode题解:Delete Node in a Linked List
- Remove Nth Node From End of List
- HtmlParser初探--使用Nodefilter和Visitor进行网页分析
- [LeetCode] Count Complete Tree Nodes
- [leetcode-116]Populating Next Right Pointers in Each Node(c++)
- cassandra nodejs driver maillist link
- Node.js详细解析
- 【LeetCode-面试算法经典-Java实现】【116-Populating Next Right Pointers in Each Node(二叉树链接右指针)】
- 我用的一些Node.js开发工具、开发包、框架等总结
- HDU 4587 TWO NODES 枚举+割点
- HDFS源码分析(一)-----INode文件节点
- NodeJS下的阿里云企业邮箱邮件发送问题
- [leetcode-117]Populating Next Right Pointers in Each Node II(c++)
- [leetcode-116]Populating Next Right Pointers in Each Node(c++)
- 【leetcode】Delete Node in a Linked List【java】
- 配置 Windows 下的 nodejs C++ 模块编译环境 安装 node-gyp