Javascript Promise node.js?

我是一个node.js新手,我想了解如何组织一些非阻塞方式的逻辑节点喜欢它。

我有一套环境['stage','prod']和另外一组参数叫做品牌['A','B','C']和一套设备['phone','tablet'] 。

在节点的callback驱动的世界中,我有这样的:

brands.forEach( function(brand) { devices.forEach( function(device) { var tapeS = getTape('stage',brand,device); // bad example...tapeS never set var tapeP = getTape('prod' ,brand,device); }) } ) // more stuff here function getTape(env,brand,device) { var req = http.request(someOptions,function(resp) { // ok, so we handle the response here, but how do I sequence this with all the other // responses, also happening asynchronously? }); } 

我正在尝试为每个环境构build一个包含块的报告:

 A: Stage -- report Prod -- report B: ... 

我的问题是,因为这里的一切都是asynchronous的,尤其是在getTape中,它调用节点的http.request。 我怎样才能在所有这个asynchronous惊喜的结尾序列化所有的东西,所以我可以按照我想要的顺序创build报告?

我听到有关JavaScript的承诺。 这是否有帮助,即收集所有这些承诺的方法,然后等待他们完成,然后获得他们收集的数据?

Q是node.js中的主要承诺实现。 我也有我自己的超轻量承诺库Promise 。 我的图书馆没有实现我在这些例子中使用的所有function,但可以使它适应不足。 Promises / A +是承诺如何工作和不合理的基础规范。 它定义了一个.then方法的行为.then并且非常可读,所以一定要在某个地方查看(不一定是直接的)。

承诺背后的想法是,他们封装了一个asynchronous值。 这使得更容易推理如何将同步代码转换为asynchronous代码,因为通常有很好的相似之处。 作为对这些概念的介绍,我会推荐我关于承诺和发电机的谈话,或者Domenic Denicola的谈话(如Promises,Promises或Callbacks,Promises和Coroutines(哦我的!) )之一。

首先要决定的是你是否想要并行地提出你的请求,或者是按顺序进行。 从这个问题我会猜测,你想同时做到这一点。 我也会假设你正在使用Q这意味着你将不得不安装它:

 npm install q 

并要求它在您使用它的每个文件的顶部:

 var Q = require('q'); 

考虑用于打印报告的理想的数据结构,我想你会有一系列的品牌,包括一系列的设备,这些设备将是具有属性stageprod对象,如:

 [ { brand: 'A', devices: [ { device: 'phone', stage: TAPE, prod: TAPE }, { device: 'tablet', stage: TAPE, prod: TAPE } ... ] }, { brand: 'B', devices: [ { device: 'phone', stage: TAPE, prod: TAPE }, { device: 'tablet', stage: TAPE, prod: TAPE } ... ] } ... ] 

我会假设,如果你有这个,那么打印出想要的报告就没有问题了。

承诺的HTTP请求

让我们看看getTape函数。 你期待它返回一个node.jsstream或包含整个下载文件的缓冲区/string吗? 无论哪种方式,在图书馆的帮助下,你会发现它更容易。 如果您是node.js的新手,我会build议请求作为一个库,只是做你所期望的。 如果你感觉更有自信, 那么substack的hyperquest是一个小得多的图书馆,可以说是整洁的,但它需要你手动处理redirect,你可能不想去。

stream媒体(困难)

stream式传输方式非常棘手。 如果您的磁带长度为100 MB,可以完成并且将需要,但承诺可能不是正确的方法。 如果这是您实际存在的问题,我很乐意详细了解这一点。

缓冲请求(容易)

要创build一个使用请求来cachingHTTP 请求并返回一个promise的函数,这非常简单。

 var Q = require('q') var request = Q.denodeify(require('request')) 

Q.denodeify只是一个捷径,说:“采取这个函数通常需要callback,并给我一个承诺的function”。

为了写getTape基础,我们做了这样的事情:

 function getTape(env, brand, device) { var response = request({ uri: 'http://example.com/' + env + '/' + brand + '/' + device, method: 'GET' }) return response.then(function (res) { if (res.statusCode >= 300) { throw new Error('Server responded with status code ' + res.statusCode) } else { return res.body.toString() //assuming tapes are strings and not binary data } }) } 

那里发生了什么request (通过Q.denodeify )正在返回一个承诺。 我们打电话给那个承诺.then(onFulfilled, onRejected) 。 这将返回一个新的转换的承诺。 如果响应承诺被拒绝(相当于throw同步代码),那么转换的承诺也是如此(因为我们没有附加onRejected处理程序)。

如果你投入其中一个处理者,转换的承诺就会被拒绝。 如果您从其中一个处理程序返回一个值,那么转换后的承诺将被“完成”(有时也称为“已解决”)。 然后.then我们可以链接更多.then然后呼吁我们转变的承诺的结束。

作为我们的function的结果,我们返回转换的承诺。

提出要求

JavaScript有一个非常有用的函数叫做.map 。 这就像.forEach但返回一个转换数组。 我将使用它保持尽可能接近原始的同步代码。

 var data = brands.map(function (brand) { var b = {brand: brand} b.devices = devices.map(function (device) { var d = {device: device} d.tapeS = getTape('stage',brand,device); // bad example...tapeS never set d.tapeP = getTape('prod' ,brand,device); return d }) }) 

现在我们有一些代码给我们提供了我在开始时提出的数据结构,除了Promise<TAPE>而不是TAPE

等待请求

Q有一个非常有用的方法叫Q.all 。 它需要一系列的承诺,并等待他们完成,所以让我们的数据结构变成一个承诺数组传递给Q.all。

一个办法是最后,我们可以通过每个项目,等待承诺解决。

 var updated = Q.all(data.map(function (brand) { return Q.all(brand.devices.map(function (device) { return Q.all([device.tapeS, device.tapeP]) .spread(function (tapeS, tapeP) { //update the values with the returned promises device.tapeS = tapeS device.tapeP = tapeP }) }) })) //if you add a line that reads `updated = updated.thenResolve(data)`, //updated would become a promise for the data structure (after being resolved) updated.then(function () { // `data` structure now has no promises in it and is ready to be printed }) 

另一个问题就是在我们去的时候这样做,以便将“发出请求”代码replace为:

 var data = Q.all(brands.map(function (brand) { var b = {brand: brand} Q.all(devices.map(function (device) { var d = {device: device} var tapeSPromise = getTape('stage',brand,device); var tapePPromise = getTape('prod' ,brand,device); return Q.all([tapeSPromise, tapePPromise]) .spread(function (tapeS, tapeP) { //now these are the actual tapes d.tapeS = tapeS d.tapeP = tapeP return d }) })) .then(function (devices) { b.devices = devices return b }) })) data.then(function (data) { // `data` structure now has no promises in it and is ready to be printed }) 

还有一种方法是使用一个小的实用程序库来进行对象的recursion深度parsing。 我没有发表它,但这个效用函数 (从Kriskowal的工作中借用的)做了一个深刻的决定,它可以让你使用:

 var data = deep(brands.map(function (brand) { var b = {brand: brand} b.devices = devices.map(function (device) { var d = {device: device} d.tapeS = getTape('stage',brand,device); // bad example...tapeS never set d.tapeP = getTape('prod' ,brand,device); return d }) })) data.then(function (data) { // `data` structure now has no promises in it and is ready to be printed }) 

获得最终数据的承诺。

我对node.js也比较陌生,最近我发现了一些能以各种方式组织asynchronouscallback特别有效的库。 然而,我最喜欢的是由caolanasynchronous 。 它有一些有用的模式,但我发现最有用的模式是async.series,async.parallel,async.waterfall。 第一个,async.series,只是按照线性顺序执行asynchronous函数:

 async.series([ function(callback){ // do some stuff ... callback(null, 'one'); }, function(callback){ // do some more stuff ... callback(null, 'two'); } ], // optional callback function(err, results){ // results is now equal to ['one', 'two'] }); 

第二个,async.parallel,只是同时执行函数:

 async.parallel([ function(callback){ setTimeout(function(){ callback(null, 'one'); }, 200); }, function(callback){ setTimeout(function(){ callback(null, 'two'); }, 100); } ], // optional callback function(err, results){ // the results array will equal ['one','two'] even though // the second function had a shorter timeout. }); 

最后一个也是我最喜欢的,就像前面提到的async.series一样,但是它也将前一个函数的结果传递给下一个:

 async.waterfall([ function(callback){ callback(null, 'one', 'two'); }, function(arg1, arg2, callback){ callback(null, 'three'); }, function(arg1, callback){ // arg1 now equals 'three' callback(null, 'done'); } ], function (err, result) { // result now equals 'done' }); 

那么,这是我的一块。 在我看来,这只是格式化节点疯狂的非阻塞体系结构的最简单的方法。 如果你需要更多的帮助,请给我一个PM。 我知道如何使用更大,更复杂的代码库来令人畏缩的node.js成为可能。

干杯。

如果你有兴趣使用承诺,你可以看看我的忠实图书馆。 它模仿许多function的asynchronousAPI,并且还具有您简要提到的“收集”function。

请注意,截至目前,faithful.parallel只接受一个数组,而不是一个散列。 这还有待执行。

承诺的另一种select是使用async模块:

 async.map(brands, function(brand, brand_cb) { async.map(brand.devices, function(device, device_cb) { async.parallel({ stage: function(cb) { // ... cb(null, stage_data) }, prod: function(cb) { // ... cb(null, prod_data) } }, function(err, data) { device_cb(null, {name: device, data: data}); }); }, function(err, data) { brand_cb(null, {name: brand, devices: data}); }); }, function(err, all_the_results) { console.log(all_the_results[0].devices[0].data.prod; }); 

作为一个初学者,现在可能需要使用callback函数和简单的stream控制库。 在掌握了callback和持续传递的风格之后,再看看承诺。

这是一个使用队列库的简单方法,例如:

 var queue = require('queue-async') var q = queue() brands.forEach(function(brand){ brand.devices.forEach(function(device){ q.defer(getTape.bind(null, 'stage', brand, device)) q.defer(getTape.bind(null, 'prod', brand, device)) }) }) q.awaitAll(function(error, results){ // use result pairs here console.log(results) })