Javascript 承诺 node.js?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16845624/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Javascript Promise node.js?
提问by Greg
I'm a node.js newbie and I'm trying to understand how I can organize some logic in the non-blocking way node likes it.
我是一个 node.js 新手,我试图了解如何以 node 喜欢的非阻塞方式组织一些逻辑。
I have a set of environments ['stage','prod'], and another set of parameters called brands ['A','B','C'] and a set of devices ['phone','tablet'].
我有一组环境 ['stage','prod'],以及另一组称为品牌 ['A','B','C'] 的参数和一组设备 ['phone','tablet'] .
In node's callback-driven world I have this:
在节点的回调驱动世界中,我有这个:
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?
});
}
I'm trying to build a report with blocks for each environment:
我正在尝试为每个环境构建一个包含块的报告:
A:
Stage -- report
Prod -- report
B: ...
My problem is that since everything here is so async, especially inside getTape, which calls node's http.request. How can I serialize everything at the end of all this async wonderment so I can create the report in the order I want?
我的问题是,由于这里的所有内容都是异步的,尤其是在调用节点的 http.request 的 getTape 内部。如何在所有这些异步奇迹结束时序列化所有内容,以便我可以按照我想要的顺序创建报告?
I heard something about javascript Promises. Would that help, i.e. some way to collect all these Promises then wait for them all to complete, then get the data they collected?
我听说了一些关于 javascript Promises 的事情。这会有所帮助,即以某种方式收集所有这些 Promise 然后等待它们全部完成,然后获取它们收集的数据吗?
回答by ForbesLindesay
Qis the dominant promise implementation in node.js. I also have my own super light weight promises library Promise. My library doesn't implement all the features I've used in these examples, but it could be made to work with minor adaptation. The underpinning specification for how promises work and ineroperate is Promises/A+. It defines the behavior for a .thenmethod and is pretty readable, so definitely give it a look at some point (not necessarily straight away).
Q是 node.js 中主要的 promise 实现。我也有自己的超轻量级承诺库Promise。我的库并没有实现我在这些示例中使用的所有功能,但可以通过较小的调整使其工作。Promises如何工作和互操作的基础规范是Promises/A+。它定义了一个.then方法的行为并且非常易读,所以一定要在某个点(不一定马上)看一看。
The idea behind promises is that they encapsulate an asynchronous value. This makes it easier to reason about how to convert synchronous code into asynchronous code because there are usually nice parallels. As an introduction to these concepts I would recommend my talk on Promises and Generatorsor one of Domenic Denicola's talks (such as Promises, Promisesor Callbacks, Promises, and Coroutines (oh my!)).
Promise 背后的想法是它们封装了一个异步值。这使得更容易推理如何将同步代码转换为异步代码,因为通常有很好的并行性。作为对这些概念的介绍,我会推荐我关于Promises 和 Generators的演讲或 Domenic Denicola 的演讲之一(例如Promises、Promises或Callbacks、Promises 和 Coroutines (oh my!))。
The first thing to decide is whether you want to make your requests in parallel, or one at a time sequenctially. From the question I'm going to guess that you want to do them in parallel. I'm also going to assume you're using Q which means you'll have to install it with:
要决定的第一件事是您是要并行提出请求,还是按顺序一次提出一个请求。从这个问题我猜你想并行做它们。我还将假设您正在使用 Q,这意味着您必须安装它:
npm install q
and require it at the top of each file in which you use it:
并在您使用它的每个文件的顶部要求它:
var Q = require('q');
Thinking about the ideal data structure to be using to print out that report, I think you'd have an array of brands, with an array of devices which would be objects with properties stageand prod, something like:
考虑用于打印该报告的理想数据结构,我认为您将拥有一系列品牌,以及一系列设备,这些设备将是具有属性stage和 的对象prod,例如:
[
{
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
}
...
]
}
...
]
I'm going to assume that if you had that then you would have no trouble printing out the desired report.
我将假设,如果您拥有它,那么您就可以轻松打印出所需的报告。
Promised HTTP Request
承诺的 HTTP 请求
Lets start by looking at the getTapefunction. Are you expecting it to return a node.js stream or a buffer/string containing the entire downloaded file? Either way, you're going to find it a lot easier with the help of a library. If you're new to node.js I'd recommend requestas a library that just does what you'd expect. If you're feeling more confident, substack's hyperquestis a much smaller library and arguably neater, but it requires you to handle things like redirects manually, which you probably don't want to get in to.
让我们从查看getTape函数开始。您是否希望它返回包含整个下载文件的 node.js 流或缓冲区/字符串?无论哪种方式,在图书馆的帮助下,您都会发现它更容易。如果您是 node.js 的新手,我建议您将request作为一个库来完成您的期望。如果你感觉更有信心,substack的hyperquest是一个小得多的库,可以说更整洁,但它需要你手动处理重定向之类的事情,你可能不想进入。
Streaming (difficult)
流媒体(困难)
The streaming approach is tricky. It can be done and will be needed if your tapes are 100s of MB long, but promises are then probably not the right way to go. I'm happy to look into this in more detail if it's an issue you actually have.
流媒体方法很棘手。如果您的磁带长达 100 MB,则可以完成并且将需要它,但是 Promise 可能不是正确的方法。如果您确实遇到了这个问题,我很乐意更详细地研究它。
Buffering with request (easy)
缓冲请求(简单)
To create a function that does a buffering HTTP request using requestand returns a promise, it's fairly simple.
要创建一个使用request缓冲 HTTP请求并返回一个 promise的函数,这相当简单。
var Q = require('q')
var request = Q.denodeify(require('request'))
Q.denodeifyis just a shortcut for saying: "take this function that normally expects a callback and give me a function that takes a promise".
Q.denodeify只是说的快捷方式:“使用这个通常需要回调的函数并给我一个接受承诺的函数”。
To write getTapebased off of that we do something like:
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
}
})
}
What's happening there is that request(via Q.denodeify) is returning a promise. We're calling .then(onFulfilled, onRejected)on that promise. This returns a new transformed promise. If the response promise was rejected (equivalent to throwin synchronous code) then so is the transformed promise (because we didn't attach an onRejectedhandler).
发生的事情是request(via Q.denodeify) 正在返回一个承诺。我们正在呼吁.then(onFulfilled, onRejected)这一承诺。这将返回一个新的转换后的承诺。如果响应承诺被拒绝(相当于throw在同步代码中),那么转换后的承诺也会被拒绝(因为我们没有附加onRejected处理程序)。
If you throw in one of the handlers, the transformed promise is rejected. If you return a value from one of the handlers then the transformed promise is "fulfilled" (also sometimes referred to as "resolved") with that value. We can then chain more .thencalls on the end of our transformed promise.
如果您投入其中一个处理程序,则转换后的 Promise 将被拒绝。如果您从其中一个处理程序返回一个值,则转换后的承诺将使用该值“实现”(有时也称为“已解决”)。然后,我们可以.then在转换后的 promise 的末尾链接更多调用。
We return the transformed promise as the result of our function.
我们返回转换后的 promise 作为我们函数的结果。
Making the requests
提出要求
JavaScript has a really helpful function called .map. It's like .forEachbut returns a transformed array. I'm going to use that to stay as close as possible to the original synchronous code.
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
})
})
Now we have code that gives us the data structure I proposed at the start, except we have Promise<TAPE>instead of TAPE.
现在我们有了代码,它为我们提供了我在开始时提出的数据结构,除了我们有Promise<TAPE>而不是TAPE.
Waiting for the requests
等待请求
Q has a really helpful method called Q.all. It takes an array of promises and waits for them all to complete, so lets turn our data structure into an array of promises to pass to Q.all.
Q 有一个非常有用的方法,称为Q.all. 它需要一组 promise 并等待它们全部完成,因此让我们将我们的数据结构转换为一组 promise 以传递给 Q.all。
One way to do this is at the end, we can go through each item and wait for the promises to resolve.
一种方法是在最后,我们可以遍历每个项目并等待承诺解决。
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
})
Another aproach would be to do it as we go, so that the "making the requests" code gets replaced with:
另一种方法是边走边做,以便“发出请求”代码被替换为:
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
})
Still another approach would be to use a small utility library that does a recursive deep-resolve of an object. I haven't got round to publishing it, but this utility function(borrowed from work by Kriskowal) does a deep resolve, which would let you use:
还有一种方法是使用一个小的实用程序库,它对对象进行递归深度解析。我还没有准备好发布它,但是这个实用程序函数(从 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
})
To get a promise for the final data.
获得对最终数据的承诺。
回答by Sandile
I'm also rather new to node.js, and I recently discovered a few libraries that are especially effective at organizing asynchronous callbacks in a variety of ways. However, by far my favorite is async by caolan. It has a few useful patterns, but the ones that I have found most useful are async.series, async.parallel, async.waterfall. The first one, async.series, just executes asynchronous functions in linear order:
我也是 node.js 的新手,最近我发现了一些在以各种方式组织异步回调方面特别有效的库。然而,到目前为止,我最喜欢的是caolan 的 async。它有一些有用的模式,但我发现最有用的是 async.series、async.parallel 和 async.waterfall。第一个,async.series,只是按线性顺序执行异步函数:
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']
});
The second, async.parallel, simply executes functions simultaneously:
第二个 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.
});
The last one, which is also my favorite, is like the previously mentioned async.series, but it also passes the results of the previous function to the next one:
最后一个,也是我最喜欢的,和前面提到的 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'
});
Well, that's my piece. This is just the simplest way to format node's crazy non-blocking architecture in my opinion. If you need any more help, send me a PM. I know how daunting node.js can become with bigger, more complex codebases.
嗯,那是我的作品。在我看来,这只是格式化 node 疯狂的非阻塞架构的最简单方法。如果您需要更多帮助,请给我发邮件。我知道随着更大、更复杂的代码库,node.js 会变得多么令人生畏。
Cheers.
干杯。
回答by Eric
An alternative option to promises would be to use the asyncmodule:
承诺的另一种选择是使用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;
});
回答by Myrne Stol
If you are interested in using promises, you could take a look at my Faithfullibrary. It mimics the Async API for a lot of functions, and also features a "collect" function which you mentioned briefly.
如果您对使用 Promise 感兴趣,可以查看我的Faithful库。它模仿了许多功能的 Async API,并且还具有您简要提到的“收集”功能。
Note that, as of now, faithful.parallel only accept an array, not a hash. That's still to be implemented.
请注意,截至目前,忠实的.parallel 只接受一个数组,而不是一个散列。那还是要执行的。
回答by Ricardo Tomasi
As a beginner, you might want to stay with callbacks and simple flow control libraries for now. Look into promises after you have a good grasp of callbacks and the continuation-passing style.
作为初学者,您现在可能希望继续使用回调和简单的流控制库。在您很好地掌握了回调和继续传递风格之后,再研究一下 promise。
Here is a simple approach using the queuelibrary, for example:
这是使用队列库的简单方法,例如:
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)
})

