【问题标题】:promises with mongoose and es6 not working as expectedmongoose 和 es6 的承诺没有按预期工作
【发布时间】:2017-01-16 16:58:40
【问题描述】:

我有以下代码,它创建一个承诺数组来保存一些数字,然后它产生承诺(使用 co 库)并打印出结果。然而,我不明白的是,当它打印输出时,它会打印相同的记录 10 次。

代码如下:

'use strict'
const Promise = require('bluebird');
const co = require('co');
const _ = require('lodash');
const mongoose = require('mongoose');

// plug in the bluebird promise library for mongoose
mongoose.Promise = Promise;

mongoose.connect('mongodb://localhost:27017/nodejs_testing');

const numSchema = new mongoose.Schema({
  num: { type: Number, required: true }
});
const Num = mongoose.model('Num', numSchema);

let promises = [];
let x;

// create an array of promises to save some numbers
for (let i = 0; i < 10; ++i) {
  let p = new Promise((resolve,reject) => {
    x = Num();
    x.num = i;
    x.save((err) => {
      if (err) {
        reject(err);
      } else {
        resolve(x);
      }
    });
  });
  promises.push(p);
};

// yield all the promises, then print out the results
co(function * () {
  let res = yield Promise.all(promises);
  _.each(res, item => {
    console.log(JSON.stringify(item));
  });
  mongoose.disconnect();
});

这是输出:

/tmp/test$ node m
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}
{"__v":0,"num":9,"_id":"57d1931037a370055f51977c"}

请注意,如果我在 Promise 中声明变量 x,那么我会得到预期的结果(例如,输出中有 10 个不同的数字)。换句话说,如果我进行此更改(见下文),它将按预期工作:

  let p = new Promise((resolve,reject) => {
    let x = Num(); // <--- declare x inside the promise
    .
    .
  });

我的问题是,为什么代码会这样?请注意,如果我使用 mongodb/mongoose 重复完全相同类型的测试 not 并仅打印一些数字,即使在 Promise 之外声明了x,它也会按预期工作。示例代码如下:

'use strict'
const Promise = require('bluebird');
const co = require('co');
const _ = require('lodash');

class Number {
  constructor(num) {
    this.num = num;
  }
};

let x;
let promises = [];

for (let i = 0; i < 10; ++i) {
  let p = new Promise((resolve,reject) => {
    setTimeout(() => {
      x = new Number(i);
      resolve(x);
    }, 300);
  });
  promises.push(p);
};

co(function * () {
  let res = yield Promise.all(promises);
  _.each(res, item => {
    console.log(JSON.stringify(item));
  });
});

输出:

/tmp/test$ node t
{"num":0}
{"num":1}
{"num":2}
{"num":3}
{"num":4}
{"num":5}
{"num":6}
{"num":7}
{"num":8}
{"num":9}

【问题讨论】:

    标签: javascript node.js mongoose promise co


    【解决方案1】:

    发生这种情况的原因是x 超出了 for 循环的范围。当您运行 for 循环时,您不会更改实例化 x 的其他变量,而是重新分配原始 x 的值。发生的情况是,最终值 x 是在将其他值保存到 Mongo 之前的 Num 值 9 并且在 x 设置为 9 之前数组中的其他 promise 没有解析。

    如果您想获得正确的输出,只需在您的 for 循环中添加 x:

    // create an array of promises to save some numbers
    for (let i = 0; i < 10; ++i) {
      let x;
      let p = new Promise((resolve,reject) => {
        x = Num();
        x.num = i;
        x.save((err) => {
          if (err) {
            reject(err);
          } else {
            resolve(x);
          }
        });
      });
      promises.push(p);
    };
    

    【讨论】:

    • @T.J.Crowder 我做到了。
    【解决方案2】:

    区别不在于 Mongoose 与非 Mongoose。您的代码正在做不同的事情。

    在你的第一个例子中,你有(见*** cmets):

    let p = new Promise((resolve,reject) => {
      x = Num();               // *** A
      x.num = i;
      x.save((err) => {
        if (err) {
          reject(err);
        } else {
          resolve(x);          // *** B
        }
       });
    });
    

    ...x 在代码所在的循环之外声明,因此所有迭代都重用该变量。

    请注意,上面标记为 A 和 B 的语句彼此异步发生。到B 发生时,所有 迭代已经完成A;因为B 看到分配给x 的last 值,这就是它用来解析的,并且它们都以相同的值解析。

    与您的第二个示例相比:

    let p = new Promise((resolve,reject) => {
      setTimeout(() => {
        x = new Number(i);     // *** A
        resolve(x);            // *** B
      }, 300);
    });
    

    请注意,这两者现在是同步发生的; B 每次解析时都使用 x 的当前值。

    这就是两者行为不同的原因。

    从根本上说,x 的声明应该更接近它的使用位置,在promise init 回调中:

    //let x;                         // *** Not here
    
    // create an array of promises to save some numbers
    for (let i = 0; i < 10; ++i) {
      let p = new Promise((resolve,reject) => {
        let x = Num();               // *** Here
        x.num = i;
        x.save((err) => {
          if (err) {
            reject(err);
          } else {
            resolve(x);
          }
         });
      });
    }
    

    记住规则是:始终在尽可能窄的范围内声明。

    【讨论】:

    • 是的,我知道最好在 promise 中声明 x 变量,这样做可以解决问题(我在问题中提到过),但我只是想了解我为什么会得到我的行为。无论如何,很好的解释,谢谢!
    猜你喜欢
    • 2018-09-19
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 1970-01-01
    • 2021-03-27
    • 2018-02-13
    • 1970-01-01
    相关资源
    最近更新 更多