简体   繁体   中英

ES6 Promise.all() strange resolution of promises array

I have a snippet (below) that will generate requests based on a couple of params. It essentially creates load similar to JBehaves by distinguishing requests per user. For the most part, this works fine. Generation of requests works as expected. The results however are not working as one would expect using Promise.all() . Which leads me to my question:

Is there an issue with Promise.all() ?

The format of the results may look a little wonky in this question but, essentially I'm just creating an array of users (which in itself is just an array of request results).

Actual Results

Instead of each object within the array being different, they're all the same. In most case, it appears to be the last object pushed into the array (but I haven't fully confirmed this). This behavior initially lead me to believe there was a scoping issue within my snippet but, I haven't been able to find it :(

[
    [{
        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
    }]
]

Expected Results

I would expect that Promise.all() would return a (promise) resolving to an array with multiple objects - each one being different as to reflect the results from each task as defined in tasks() .

[
    [{
        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
    }]
]

Code Snippet

If you notice the commented out console.dir(stats) : this line spits out the results as expected (different results per task) but, if I slap a .then() on the end of the reduce, the array is returned as Actual Results (vs. Expected Results )

(For brevity, let's assume request() returns a promise)

'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;
  }
};

Request Snippet

'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;

Using

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

Any help would be greatly appreciated!

Is there an issue with Promise.all() ?

No.

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

Indeed. They are all the same object.

Your request function does - for whatever reason - resolve its returned promise with its argument. As you are passing the very same tests[id] object to all requests of a batch, they all will end up with this object.

Your console.dir did show the expected results because request does mutate its argument - the test object contains different values after every call, which are subsequently logged, before being overwritten in the next call.

You should change that cb to create a new object, instead of mutating 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);
  });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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