[Node.js] Promise,Q及Async
原文地址:http://www.moye.me/2014/12/27/promise_q_async/
引子
在使用Node/JS编程的时候,经常会遇到这样的问题:有一连串的异步方法,需要按顺序执行,前后结果之间有依赖关系,形如(片断1):
asyncTask(initial, function (err, result) {//step 1
if (err)
throw err;
asyncTask(result, function (err, result2) {//step 2
if (err)
throw err;
asyncTask(result2, function (err, result3) {//final
if (err)
throw err;
console.log(result3);
});
});
});
之前也介绍过,这就是著名的回调地狱(Pyramid of Doom)。
Promise
解决回调嵌套及串行状态传送问题,是有规范可循的,如 CommonJS的Promise规范 。
Promise是对异步编程的一种抽象。它是一个代理对象,代表一个必须进行异步处理的函数返回的值或抛出的异常。
以实现较多的 Promise/A(thenable)来说:
- Promise作为一个对象,代表了一次任务的执行,它有三种状态:成功/失败/执行中
- Promise对象有一个 then接口(原型函数),形如:
then(fulfilledHandler, errorHandler, progressHandler)
。这三个参数在Promise对象完成任务时被执行,它们对应状态为成功/失败/执行中——我们可以把then方法等同于Promise对象的构造器,fulfilledHandler
、errorHandler
及progressHandler
可以对应到Promise.resolve(val)
、Promise.reject(reason)
及Promise.notify(update)
- Promise对象还有一个catch接口(原型函数),形如:
catch(onRejected)。
onRejected为错误处理的回调,接收从Promise.reject(reason)
传递过来的错误信息 - Promise对象暴露给调用方的then接口,是一个永远可以返回Promise对象自身的函数,所以then可以继续链式调用then,直到任务完成——简单说,Promise执行的结果可以传递给下一个Promise对象,这在异步任务的串行化中非常有用(并且我们不用担心这些Promise在执行任务时产生副作用,根据规范,每一个Promise与被调函数间都是相互独立的
- Promise对象的内部,维护一个队列,在构造执行链完成时,将待执行函数存入需要依次执行的任务——什么时候算完成呢?一般的Promise库实现,都需要在构造链的尾部调用一个done之类的函数,以明示构造链的终结。
Promise做为类和对象的细节,MDN上的描述更为详尽。回到实现层面,先来看一个开源的Promise框架,Q:
Q
Q是一个对Promise/A规范实现较为完备的开源框架。
针对前述的代码片断1 场景,Q提供了这样的可能性:Q.promise能将异步逻辑包装成一个thenable函数,从而注入它实现的回调函数,源码形如:
Q.promise = promise;
function promise(resolver) {
if (typeof resolver !== "function") {
throw new TypeError("resolver must be a function.");
}
var deferred = defer();
try {
resolver(deferred.resolve,
deferred.reject, deferred.notify);
} catch (reason) {
deferred.reject(reason);
}
return deferred.promise;
}
这个resolver就是我们的异步逻辑封装函数, 我们可以选择性的接收resolve和reject作为参数,并在异步方法完成/出错时进行回调,让Q获得流程控制权。
Q的异步串行例子
假设有两个文本文件:1.txt 和 2.txt,内容分别为:I'm the first text.\n
和I'm the second text.\n
。我们需要顺序且异步的读取它们,在全部读取完成/出错时,显示相应信息。
首先,需要将异步方法进行包装(片断2):
var Q = require('q');
var path = require('path');
var fs = require('fs'); function readFile(previous, fileName) {
return Q.promise(function (resolve, reject) {
fs.readFile(path.join(process.cwd(), fileName),
function (error, text) {
if (error) {
reject(new Error(error));
} else {
resolve(previous + text.toString());
}
});
});
}
fs.readFile 读取文件,成功调用Q.defer.resolve,出错调用Q.defer.reject。readFile做为用于串联Promise对象的方法,提供了一个previous状态参数,用于累加上次执行的结果。有了这个方法,基于then的串行逻辑就能这样实现:
readFile('', '1.txt')
.then(function (previous) {
return readFile(previous, '2.txt');
})
.then(function (finalText) {
console.log(finalText);
})
.catch(function (error) {
console.log(error);
})
.done();
可以看出,thenable函数的链式调用总是能将上一个Promise.resolve的结果做为参数传入。
Async
Async 严格说起来不是一个Promise的实现,而是一个异步工具集(utilities),通过源码我们能看得很清楚,它导出了非常多的方法,集合/流程控制 都有涉及。
针对前述的代码片断1 场景,Async提供了若干种方法,挑两个有代表性的:
Async.waterfall
waterfall形如waterfall(tasks, [callback])
,tasks是一个function数组,[callback]参数是最终结果的回调。tasks数组里的函数按顺序执行,当前任务可以接收上一个任务执行的结果,看个例子:
var async = require('async');
var path = require('path');
var fs = require('fs'); function readFile4WaterFall(previous, fileName, callback) {
fs.readFile(path.join(process.cwd(), fileName),
function (error, text) {
callback(error, previous + text.toString());
});
}
async.waterfall(
[
function (callback) {
readFile4WaterFall('', '1.txt', callback)
},
function (previous, callback) {
readFile4WaterFall(previous, '2.txt', callback);
}
], function (err, result) {
console.log(result);
}
);
可以看出,不管是何种形式的异步流程控制,都需要注入实现的回调(这里是function(callback)),以获取流程控制权。运行结果:
I'm the first text.
I'm the second text.
Async.series
series形如series(tasks, [callback])
,和waterfall不同,tasks数组里的函数按顺序执行,每个任务只接受一个callback注入,并不能传递上一次任务执行的结果。每一个函数执行的结果,都被push到了一个result数组里,也就是[callback(error, result)]的第二个参数。例子:
var async = require('async');
var path = require('path');
var fs = require('fs'); function readFile4Series(fileName, callback) {
fs.readFile(path.join(process.cwd(), fileName),
function (error, text) {
callback(error, text.toString());
});
}
async.series(
[
function (callback) {
readFile4Series('1.txt', callback)
},
function (callback) {
readFile4Series('2.txt', callback);
}
], function (err, result) {
console.log(result);
}
);
运行结果:
[ 'I\'m the first text.\n', 'I\'m the second text.\n' ]
动态的异步串行
老实说,上面的示例仅仅展示了异步框架的威力,却并不实用:在实践中,我们遇到的情况并不是事先构造好要执行的函数链,而是代码动态决定要执行哪些函数,即 .then 是动态拼接出来的。
以前示Q的片断2 为例,要读取的文件名存在数组里,我们需要针对文件名构造执行链:
//要读取的文件数组
var files = ['1.txt', '2.txt', '3.txt'];
//要构造的Promise链
var tasks = [];
readFile高阶函数需要稍加改造,因为不是显式的构造链,原 .then 传递上次执行的函数需要嵌入到高阶函数中:
function readFileDynamic(fileName) {
return function(previous) { //.then callback
return Q.promise(function (resolve, reject) {
fs.readFile(path.join(process.cwd(), fileName),
function (error, text) {
if (error) {
reject(new Error(error));
} else {
resolve(previous + text.toString());
}
});
});
}
}
构造任务链和执行链:
files.forEach(function (fileName) {
tasks.push(readFileDynamic(fileName));
}); var result = Q('');
tasks.forEach(function (f) {
result = result.then(f);
});
调用:
result
.then(function (finalText) {
console.log(finalText);
})
.catch(function (error) {
console.log(error);
})
.done();
如此,借助Q就可实现动态的异步串行链,本质和静态构造执行链无二致,只是Promise构造形式进行了转换。至于Async就更简单了,前述的 waterfall/series的 tasks本就是个数组,天然动态。
更多文章请移步我的blog新地址: http://www.moye.me/
[Node.js] Promise,Q及Async的更多相关文章
- 【干货分享】Node.js 中文学习资料和教程导航
这篇文章来自 Github 上的一位开发者收集整理的 Node.js 中文学习资料和教程导航.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念,它的目标是帮助程 ...
- node.js中文资料导航 Mark
Node.js HomePage Infoq深入浅出Node.js系列(进阶必读) Node.js中文文档 被误解的 Node.js Node.js C++ addon编写实战系列 热门node.js ...
- 【干货分享】Node.js 中文资料导航
这篇文章与大家分享一批高质量的的 Node.js 中文资料.Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的, 易于扩展的网络应用 Node ...
- node.js中文资料导航
以下资料来自gitHUb上面:https://github.com/youyudehexie/node123 Node.js HomePage Node官网七牛镜像 Infoq深入浅出Node.js系 ...
- Node.js 中文学习资料和教程导航
这篇文章来自 Github 上的一位开发者收集整理的 Node.js 中文学习资料和教程导航.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念,它的目标是帮助程 ...
- 为Node.js编写组件的几种方式
本文主要备忘为Node.js编写组件的三种实现:纯js实现.v8 API实现(同步&异步).借助swig框架实现. 关键字:Node.js.C++.v8.swig.异步.回调. 简介 首先介绍 ...
- 我的Node.js学习历程
学习一门技术,每个人都有每个人的方法.我的方法很简单,做项目. 基本概念 在搭建一个node网站之前,还是要掌握一些基本的概念的,这里列举一下,具体的内容大家自己到网上去查: npm bower ex ...
- 实战系列之 Node.js 玩转 Java
这些年以来,Node.js的兴起,JavaScript已经从当年的“世界最被误解的语言”变成了“世界最流行的语言”.且其发展之势,从语言本身的进化,库和包的增长,工具支持的完善,star项目和领域解决 ...
- Node.js 101(2): Promise and async
--原文地址:http://blog.chrisyip.im/nodejs-101-package-promise-and-async 先回想一下 Sagase 的项目结构: lib/ cli.js ...
随机推荐
- [原创]上海好买基金招高级Java技术经理/运维主管/高级无线客户端开发等职位(内推)
[原创]上海好买基金招高级Java技术经理/运维主管/高级无线客户端开发等职位(内推) 内部推荐职位 高级JAVA技术经理: 岗位职责: 负责项目管理(技术方向),按照产品开发流 ,带领研发团队,制定 ...
- Mars的自语重出江湖,祝大家端午节安康
上一篇博客似乎已是非常久远的回忆了,不再码字也已经很多年.<三国演义>里,刘备投靠曹操的那段时间里,2个兄弟问刘备未来,刘备说: 屈身守分,以待天时,不可与命争也. 这样一个时代,每个老百 ...
- JNI开发示例
安装:eclipse(http://www.eclipse.org/).CDT(C/C++ Development Tooling).ADT(Android Development Tools) ht ...
- 【CUDA学习】共享存储器
下面简单介绍一些cuda中的共享存储器和全局存储器 共享存储器,shared memory,可以被同一块中的所有线程访问的可读写存储器,生存期是块的生命期. Tesla的每个SM拥有16KB共享存储器 ...
- C#中使用委托、接口、匿名方法、泛型委托实现加减乘除算法
使用C#实现加减乘除算法经常被用作新手练习.本篇来分别体验通过委托.接口.匿名方法.泛型委托来实现. 使用委托实现 加减乘除拥有相同的参数个数.类型和返回类型,首先想到了使用委托实现. //创建一个委 ...
- Swift入门篇-集合
一:数组 一:可变数组 定义:数组使用有序列表存储相同类型的多重数据. 格式: 第一种格式 var 变量: 类型[] = [变量值,变量值,...] 第二种格式 var 变量 =[变量值,变量值,.. ...
- linux服务器调整参数支持高并发
服务端调整系统的参数,在/etc/sysctl.conf中: ◦net.core.somaxconn = 2048◦net.core.rmem_default = 262144◦net.core.wm ...
- int跟byte[]数组互转的方法,整数 + 浮点型
整数: int转byte数组 public static byte[] intToBytes2(int n){ ]; ;i < ;i++) { b[i]=(-i*)); } return b; ...
- embarcadero radstudio xe5 正式版 下载地址
http://altd.embarcadero.com/download/radstudio/xe5/delphicbuilder_xe5_win.iso
- CentOS 7.2 安装配置 Percona Server
个人比较喜欢 MYSQL 的轻量,今天花了一点时间把阿里云上的 MYSQL5.7 换成了 Percona-Server ,Percona 是一个开源的 MySQL 衍生版.InnoDB的数据库引擎使得 ...