简体   繁体   English

Javascript Promise node.js?

[英]Javascript Promise node.js?

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新手,我试图理解如何以非阻塞的方式组织一些逻辑节点喜欢它。

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']和一组设备['手机','平板电脑']的参数。

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. 我的问题是,因为这里的所有内容都是如此异步,特别是在调用节点的http.request的getTape中。 How can I serialize everything at the end of all this async wonderment so I can create the report in the order I want? 如何在所有这些异步奇迹结束时序列化所有内容,以便按照我想要的顺序创建报告?

I heard something about javascript Promises. 我听说过javascript Promises。 Would that help, ie some way to collect all these Promises then wait for them all to complete, then get the data they collected? 这会有所帮助,即收集所有这些Promises的某种方式然后等待它们全部完成,然后获取它们收集的数据?

Q is the dominant promise implementation in node.js. Q是node.js中的主要承诺实现。 I also have my own super light weight promises library Promise . 我也有自己的超轻量级承诺库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+ . Promises / A +承诺如何运作和不合理的基础规范。 It defines the behavior for a .then method and is pretty readable, so definitely give it a look at some point (not necessarily straight away). 它定义了.then方法的行为并且非常易读,因此请.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 Generators or one of Domenic Denicola's talks (such as Promises, Promises or Callbacks, Promises, and Coroutines (oh my!) ). 作为这些概念的介绍,我会推荐我关于Promises and Generators的演讲或者Domenic Denicola的演讲(如Promises,PromisesCallbacks,Promises和Coroutines(哦,我的!) )。

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 stage and prod , something like: 考虑到用于打印该报告的理想数据结构,我认为您将拥有一系列品牌,其中包含一系列设备,这些设备将成为具有属性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
        }
        ...
      ]
  }
  ...
]

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 getTape function. 让我们从查看getTape函数开始。 Are you expecting it to return a node.js stream or a buffer/string containing the entire downloaded file? 您是否希望它返回node.js流或包含整个下载文件的缓冲区/字符串? 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 request as a library that just does what you'd expect. 如果您是node.js的新手,我建议将请求作为一个可以满足您期望的库。 If you're feeling more confident, substack's hyperquest is 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. 如果你感觉更自信, 那么substackhyperquest是一个小得多的库,可以说是更整洁,但它要求你手动处理重定向等事情,你可能不想进入。

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. 它可以完成,如果您的磁带长度为100 MB,则需要它,但承诺可能不是正确的方法。 I'm happy to look into this in more detail if it's an issue you actually have. 如果这是你实际遇到的问题,我很乐意更详细地研究这个问题。

Buffering with request (easy) 缓冲请求(简单)

To create a function that does a buffering HTTP request using request and returns a promise, it's fairly simple. 要创建一个使用请求缓冲HTTP 请求并返回promise的函数,它非常简单。

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

Q.denodeify is 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 getTape based 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. 发生的事情是, request (通过Q.denodeify )返回一个承诺。 We're calling .then(onFulfilled, onRejected) on that promise. 我们在那个承诺上打电话给.then(onFulfilled, onRejected) This returns a new transformed promise. 这将返回一个新转换的promise。 If the response promise was rejected (equivalent to throw in synchronous code) then so is the transformed promise (because we didn't attach an onRejected handler). 如果响应promise被拒绝(相当于throw同步代码)那么转换后的promise也是如此(因为我们没有附加onRejected处理程序)。

If you throw in one of the handlers, the transformed promise is rejected. 如果你输入其中一个处理程序,转换后的promise将被拒绝。 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. 如果从其中一个处理程序返回一个值,那么转换后的promise将被“履行”(有时也称为“已解决”)并带有该值。 We can then chain more .then calls on the end of our transformed promise. 然后.then我们可以链接更多。然后在我们转变的承诺结束时进行调用。

We return the transformed promise as the result of our function. 作为函数的结果,我们返回转换后的promise。

Making the requests 提出要求

JavaScript has a really helpful function called .map . JavaScript有一个非常有用的函数叫做.map It's like .forEach but returns a transformed array. 它就像.forEach但返回一个转换后的数组。 I'm going to use that to stay as close as possible to the original synchronous code. 我将使用它尽可能接近原始的同步代码。

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 . Q有一个非常有用的方法叫做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. 它需要一组promises并等待它们全部完成,所以让我们将数据结构转换为一个promises数组,传递给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. 获得最终数据的承诺。

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. 我也是node.js的新手,我最近发现了一些在以各种方式组织异步回调时特别有效的库。 However, by far my favorite is async by caolan . 然而,到目前为止,我最喜欢的是caolan异步 It has a few useful patterns, but the ones that I have found most useful are async.series, async.parallel, async.waterfall. 它有一些有用的模式,但我发现最有用的是async.series,async.parallel,async.waterfall。 The first one, async.series, just executes asynchronous functions in linear order: 第一个是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. 如果您需要更多帮助,请寄给我一个PM。 I know how daunting node.js can become with bigger, more complex codebases. 我知道node.js在更大,更复杂的代码库中会变得多么令人生畏。

Cheers. 干杯。

An alternative option to promises would be to use the async module: 承诺的另一种选择是使用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;
});

If you are interested in using promises, you could take a look at my Faithful library. 如果您对使用promises感兴趣,可以查看我的忠实图书馆。 It mimics the Async API for a lot of functions, and also features a "collect" function which you mentioned briefly. 它模仿了许多函数的Async API,并且还具有您简要提到的“收集”功能。

Note that, as of now, faithful.parallel only accept an array, not a hash. 请注意,截至目前,faithful.parallel只接受数组,而不是哈希。 That's still to be implemented. 这还有待实施。

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. 在掌握了回调和延续传递风格之后,先了解承诺。

Here is a simple approach using the queue library, 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)
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM