如果与 bluebird map 函数一起使用,Iteratee 函数将在 javascript 中丢失对该对象的引用

Iteratee function loses reference to this object in javascript if used with bluebird map function

我正在使用 bluebird npm 包来处理我的应用程序中的异步工作负载。假设 processAllItems 函数用于触发所有项目的处理。列表中的每一项都需要使用函数 processOneItem 逐一处理。它在内部从同一个 class 调用了几个函数。 Bluebird 库的 .map 函数用于管理此处的执行并发。所以,我的代码看起来像这样。

const Bluebird = require('bluebird');

module.exports = function (app) {
  return {

    doSomething: function () {
      return new Promise(function (resolve, reject) {
        // do something and resolve
      });
    },

    processOneItem: function (item) {
      let self = this;
      return new Promise(function (resolve, reject) {
        // blah
        self.doSomething()  //self is undefined
          .then(function () {
            //do something else and so on...
          })
      });
    },

    processAllItems: function () {
      const self = this;
      return new Promise(function (resolve, reject) {
        // processOneItem function is to be called for an array of data
        // lets assume
        let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        Bluebird.map(array, self.processOneItem, { concurrency: 5 })
          .then(function () {
            return resolve();
          })
          .catch(function (error) {
            return reject(error);
          });

      });
    }
  };
};

由于函数 doSomethingprocessOneItemprocessAllItems 是同一对象的键,它们需要由 this 对象访问,该对象分配给 self变量。到目前为止,从 javascript 的角度来看,这一切都是有道理的。但是当我执行这段代码时,我在函数 processOneItem 内的行 self.doSomething() 上收到错误提示 Cannot read property 'doSomething'。对 this 对象的引用怎么可能丢失了?我是不是做错了什么?

我注意到一件事,如果我将 iteratee 函数包装在另一个未命名的函数中,这似乎工作得很好。

const Bluebird = require('bluebird');

module.exports = function (app) {
  return {

    doSomething: function () {
      return new Promise(function (resolve, reject) {
        // do something and resolve
      });
    },

    processOneItem: function (item) {
      let self = this;
      return new Promise(function (resolve, reject) {
        // blah
        self.doSomething()  //self reference is maintained, works perfectly fine.
          .then(function () {
            //do something else and so on...
          })
      });
    },

    processAllItems: function () {
      const self = this;
      return new Promise(function (resolve, reject) {
        // processOneItem function is to be called for an array of data
        // lets assume
        let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        Bluebird.map(array, function (record) { return self.processOneItem(record); }, { concurrency: 5 })
          .then(function () {
            return resolve();
          })
          .catch(function (error) {
            return reject(error);
          });

      });
    }
  };
};

怎么可能当行

Bluebird.map(array, self.processOneItem, { concurrency: 5 })

替换为行

Bluebird.map(array, function (record) { return self.processOneItem(record); }, { concurrency: 5 })

this 对象的引用在 iteratee 函数中维护,代码工作得很好。

这是javascript绑定的问题。这是对上述案例的简化,无论蓝鸟如何,它都会产生相同的结果。

const obj = {
    a: function(){
        console.log('A THIS!', this)
    },
    
    b: function(){
        console.log('B THIS!', this)
    }

}

function c_function(paramsFunction){
    paramsFunction();
}


obj.a() // A THIS!, {a: function(), b: function()}
obj.b() // b THIS!, {a: function(), b: function()}

c_function(obj.a) //"A THIS!" global {Buffer: function(), clearImmediate: function(), clearInterval: function(), clearTimeout: function(), …}


c_function(function() { obj.a()}) // A THIS!, {a: function(), b: function()}

函数执行的 javascript 的 this 在该函数执行时绑定。

  1. 列表项

所以调用时

obj.a()

它检查 this 规则并发现它是在对象上调用的,因此 this 将是 obj

  1. 调用时
c_function(obj.a)

c_function 接收一个函数作为参数。当它执行函数时,它会尝试绑定 this。就 c_function 作用域而言,该函数不会在对象上调用。因此它将根据规则绑定 this。这里 c_function 绑定到 global this 并执行 paramsFunction() 绑定将是相同的。

  1. 调用时
c_function(function() { obj.a()}) // Or
c_function(() => { obj.a()})

前2个知识全部合并。重要的是,在通话的那一刻,您是从 obj

打来的

额外 请注意,地图函数可能会使用 bindcallapply 调用您的函数。这将影响执行函数的范围。

function c_function_binding(paramsFunction) {
    paramsFunction.call({}, paramsFunction)
}

c_function_binding(obj.a) // "A THIS!" {}

我找不到它,但几年前 Kyle Simpson 开设了一门课程或研讨会,详细解释了 javascript 中的范围绑定及其所有怪癖。