Javascript Promise node.js?

Gre*_*reg 14 javascript node.js promise

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

我有一套环境['stage','prod'],以及另一组称为品牌['A','B','C']和一组设备['手机','平板电脑']的参数.

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

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?
   });
}
Run Code Online (Sandbox Code Playgroud)

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

A:
    Stage -- report
    Prod  -- report 
B:    ...
Run Code Online (Sandbox Code Playgroud)

我的问题是,因为这里的所有内容都是如此异步,特别是在调用节点的http.request的getTape中.如何在所有这些异步奇迹结束时序列化所有内容,以便按照我想要的顺序创建报告?

我听说过javascript Promises.这会有所帮助,即收集所有这些Promises的某种方式然后等待它们全部完成,然后获取它们收集的数据?

For*_*say 39

Q是node.js中的主要承诺实现.我也有自己的超轻量级承诺库Promise.我的库没有实现我在这些示例中使用的所有功能,但可以使用它进行微调.Promises/A +承诺如何运作和不合理的基础规范.它定义了一个.then方法的行为并且非常易读,所以一定要看看它(不一定要直接).

承诺背后的想法是它们封装了一个异步值.这使得更容易推理如何将同步代码转换为异步代码,因为通常有很好的相似之处.作为这些概念的介绍,我会推荐我关于Promises and Generators的演讲或者Domenic Denicola的演讲(如Promises,PromisesCallbacks,Promises和Coroutines(哦,我的!)).

首先要决定的是,您是要并行提出请求,还是要按顺序提出请求.从问题我猜你想要并行完成它们.我也会假设你正在使用Q,这意味着你必须安装它:

npm install q
Run Code Online (Sandbox Code Playgroud)

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

var Q = require('q');
Run Code Online (Sandbox Code Playgroud)

关于理想的数据结构考虑使用打印出的报告是,我认为你有琳琅满目的品牌,与器件阵列,其将与属性的对象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
        }
        ...
      ]
  }
  ...
]
Run Code Online (Sandbox Code Playgroud)

我会假设如果你有那么你就可以毫不费力地打印出所需的报告.

承诺的HTTP请求

让我们从查看getTape功能开始.您是否希望它返回node.js流或包含整个下载文件的缓冲区/字符串?无论哪种方式,你都可以在图书馆的帮助下轻松找到它.如果您是node.js的新手,我建议将请求作为一个可以满足您期望的库.如果你感觉更自信,那么substackhyperquest是一个小得多的库,可以说是更整洁,但它要求你手动处理重定向等事情,你可能不想进入.

流媒体(困难)

流媒体方法很棘手.它可以完成,如果您的磁带长度为100 MB,则需要它,但承诺可能不是正确的方法.如果这是你实际遇到的问题,我很乐意更详细地研究这个问题.

缓冲请求(简单)

要创建一个使用请求缓冲HTTP 请求并返回promise 的函数,它非常简单.

var Q = require('q')
var request = Q.denodeify(require('request'))
Run Code Online (Sandbox Code Playgroud)

Q.denodeify 只是一个说法的捷径:"采取这个通常需要回调的函数,给我一个承诺的函数".

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
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

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

如果你输入其中一个处理程序,转换后的promise将被拒绝.如果从其中一个处理程序返回一个值,那么转换后的promise将被"履行"(有时也称为"已解决")并带有该值.然后,我们可以.then在转化的承诺结束时链接更多的调用.

作为函数的结果,我们返回转换后的promise.

提出要求

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
  })
})
Run Code Online (Sandbox Code Playgroud)

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

等待请求

Q有一个非常有用的方法叫做Q.all.它需要一组promises并等待它们全部完成,所以让我们将数据结构转换为一个promises数组,传递给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
})
Run Code Online (Sandbox Code Playgroud)

另一种方法是在我们去的时候这样做,以便"制作请求"代码被替换为:

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
})
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用一个小的实用程序库来执行对象的递归深度解析.我还没有发布它,但是这个效用函数(从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
})
Run Code Online (Sandbox Code Playgroud)

获得最终数据的承诺.