ES6 Promise.all() 承诺数组的奇怪解析

ES6 Promise.all() strange resolution of promises array

我有一个片段(如下),它会根据几个参数生成请求。它本质上通过区分每个用户的请求来创建类似于 JBehaves 的负载。在大多数情况下,这工作正常。请求的生成按预期工作。然而,结果并不像使用 Promise.all() 时预期的那样有效。这引出了我的问题:

Promise.all() 有问题吗?

这个问题的结果格式可能看起来有点奇怪,但实际上我只是在创建一个用户数组(它本身就是一个请求结果数组)。

实际结果

数组中的每个对象都相同,而不是不同。在大多数情况下,它似乎是最后一个被推入数组的对象(但我还没有完全证实这一点)。这种行为最初让我相信我的代码片段中存在范围界定问题,但我一直无法找到它:(

[
    [{
        hostname: 'google.com',
        headers: [Object],
        path: '/url1/',
        method: 'GET',
        date: 1457395032277,
        status: 200,
        ttfb: 1488
    }, {
        hostname: 'google.com',
        headers: [Object],
        path: '/url1/',
        method: 'GET',
        date: 1457395032277,
        status: 200,
        ttfb: 1488
    }]
]

预期结果

我希望 Promise.all() 会 return 一个(承诺)解析为一个包含多个对象的数组——每个对象都是不同的,以反映 [=19] 中定义的每个任务的结果=].

[
    [{
        hostname: 'google.com',
        headers: [Object],
        path: '/url1/',
        method: 'GET',
        date: 1457395032277,
        status: 200,
        ttfb: 1488
    }, {
        hostname: 'bing.com',
        headers: [Object],
        path: '/url2/',
        method: 'GET',
        date: 1457395032280,
        status: 500,
        ttfb: 501
    }]
]

代码片段

如果您注意到注释掉的 console.dir(stats):此行按预期吐出结果(每个任务的结果不同)但是,如果我在 reduce 的末尾打一个 .then(),数组被 return 编辑为 Actual Results(对比 Expected Results

(为简洁起见,我们假设 request() return 是一个承诺)

'use strict';

const request = require('./request');
const Promise = require('bluebird');

module.exports = (tests, options) => {
  return Promise.all(users());

  ////////////

  /* generate users */
  function users() {
    let users = [];

    for (let x = options.users; x > 0; x--) {
      /* https://github.com/petkaantonov/bluebird/issues/70#issuecomment-32256273 */
      let user = Promise.reduce(tasks(), (values, task) => {
        return task().then((stats) => {
          // console.dir(stats);
          values.push(stats);
          return values;
        });
      }, []);

      users.push(user);
    };

    return users;
  }

  /* generate requests per user */
  function tasks() {
    let tasks = [];

    for (let id of Object.keys(tests)) {
      for (let x = options.requests; x > 0; x--) {
        let task = () => {
          let delay = options.delay * 1000;
          return Promise.delay(delay).then(() => request(tests[id]));
        };

        tasks.push(task);
      };
    }

    return tasks;
  }
};

请求代码段

'use strict';

const https = require('follow-redirects').https;
const sprintf = require('util').format;
const Promise = require('bluebird');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;

let request = (req) => {
  return new Promise((resolve) => {
    let start = Date.now();
    let ttfb;

    let cb = (res) => {
      req.status = res.statusCode;

      res.on('data', (chunk) => {
        if (!ttfb) ttfb = Date.now() - start;
      });

      res.on('end', () => {
        req.ttfb = ttfb;
        req.end = Date.now() - start;
        resolve(req);
      });

      res.on('error', (err) => {
        req.error = err;
        resolve(req);
      });
    };

    /* convert cookies for convenience */
    if (req.headers.cookies) {
      let cookies = [];
      for (let cookie of Object.keys(req.headers.cookies)) {
        cookies.push(sprintf('%s=%s', cookie, req.headers.cookies[cookie]));
      }
      req.headers.cookie = cookies.join('; ');
      req.cookies = req.headers.cookies;
      delete req.headers.cookies;
    }

    https.request(req, cb).end();
  });
};

module.exports = request;

使用

$ npm --version
2.14.12
$ node --version
v0.12.9

如有任何帮助,我们将不胜感激!

Is there an issue with Promise.all()?

没有。

Instead of each object within the array being different, they're all the same.

确实如此。它们都是同一个对象。

您的 request function 确实 - 无论出于何种原因 - 用其参数解决其返回的承诺。当您将同一个 tests[id] 对象传递给批处理的所有请求时,它们最终都会以该对象结束。

您的 console.dir 确实显示了预期的结果,因为 request 确实改变了它的参数 - 测试对象在每次调用后包含不同的值,这些值随后被记录下来,然后在下一次调用中被覆盖。

您应该更改 cb 以创建一个新对象,而不是改变 req:

function cb(response) {
  let result = {
    status: response.statusCode
  };

  response.on('data', (chunk) => {
    if (!ttfb) ttfb = Date.now() - start;
  });

  response.on('end', () => {
    result.ttfb = ttfb;
    result.end = Date.now() - start;
    resolve(result);
  });

  response.on('error', (err) => {
    result.error = err;
    resolve(result);
  });
}