一尘不染

Javascript Promise node.js?

node.js

我是一个node.js新手,我试图了解如何以非阻塞方式像节点一样组织一些逻辑。

我有一组环境[‘stage’,’prod’],还有另一组称为品牌[‘A’,’B’,’C’]的参数和一组设备[‘phone’,’tablet’] 。

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

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?
   });
}

我正在尝试为每个环境构建带有块的报告:

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

我的问题是,由于这里的所有内容都是如此异步的,尤其是在getTape内部,它调用了节点的http.request。如何在所有这些异步问题的结尾处序列化所有内容,以便可以按所需顺序创建报告?

我听说过有关JavaScript Promises的内容。那会有所帮助吗,例如某种方式来收集所有这些承诺,然后等待它们全部完成,然后获取它们收集的数据?


阅读 233

收藏
2020-07-07

共1个答案

一尘不染

Q是node.js中主要的promise实现。我也有自己的超轻量承诺库Promise。我的库没有实现我在这些示例中使用的所有功能,但是可以使其稍作改动就可以使用。Promises
/ A +
是关于诺言如何工作和无效的基础规范。它定义了一种.then方法的行为,并且可读性很强,因此绝对可以从某种角度(不一定是马上开始)来看一下它。

promises的思想是它们封装了一个异步值。因为通常会有很好的并行处理,所以这使得更容易推理如何将同步代码转换为异步代码。作为对这些概念的介绍,我将推荐关于Promises
and
Generators的
演讲,或者Domenic
Denicola的演讲之一(例如Promises,PromisesCallbacks,Promises和Coroutines(哦,我的!))。

首先要决定的是要并行执行请求还是一次连续请求。从这个问题中,我将猜测您想并行执行它们。我还要假设您正在使用Q,这意味着您必须使用以下命令进行安装:

npm install q

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

var Q = require('q');

考虑到用于打印报告的理想数据结构,我认为您会有一系列品牌,以及一系列设备,这些设备将是具有属性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
        }
        ...
      ]
  }
  ...
]

我要假设的是,如果您有那样的话,则可以轻松打印出所需的报告。

承诺的HTTP请求

让我们先看一下getTape函数。您是否希望它返回包含整个下载文件的node.js流或缓冲区/字符串?无论哪种方式,您都可以在库的帮助下轻松找到它。如果您不熟悉node.js,我建议您将request作为一个可以满足您期望的库。如果您感到更自信,则substackhyperquest是一个较小的库,可以说更整洁,但是它需要您手动处理诸如重定向之类的事情,您可能不想进入。

流式传输(困难)

流式传输方法很棘手。如果您的磁带长度为100兆MB,则可以完成并且将需要这样做,但是承诺可能不是正确的选择。如果您确实遇到此问题,我很乐意对此进行更详细的研究。

根据请求进行缓冲(简单)

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

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

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
    }
  })
}

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

如果您抛出其中一个处理程序,则转换后的promise将被拒绝。如果您从处理程序之一返回一个值,则转换后的promise将用该值“实现”(有时也称为“已解析”)。然后,我们可以.then在转换后的承诺结束时链接更多的呼叫。

我们将转换后的承诺作为函数的结果返回。

提出要求

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。它需要一个promise数组,并等待它们全部完成,因此让我们将数据结构转换为一个promise数组以传递给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
})

另一个方法是随我们去做,以便将“发出请求”代码替换为:

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
})

还有一种方法是使用小型实用程序库,该库对对象进行递归深度解析。我还没有全面发布它的方法,但是这个实用程序功能(由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
})

获得最终数据的保证。

2020-07-07