Функция Iteratee теряет ссылку на этот объект в javascript, если используется с функцией карты bluebird - PullRequest
0 голосов
/ 06 августа 2020

Я использую пакет bluebird npm для обработки асинхронных рабочих нагрузок в моем приложении. Предположим, что функция processAllItems используется для запуска обработки всех элементов. Каждый элемент из списка необходимо обработать с помощью функции processOneItem, которая обрабатывает их один за другим. Он внутренне вызывает несколько функций из одного класса. Здесь для управления параллелизмом выполнения используется функция .map библиотеки Bluebird. Итак, мой код выглядит примерно так.

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

      });
    }
  };
};

Поскольку функции doSomething, processOneItem и processAllItems являются ключами одного и того же объекта, к ним должен получить доступ объект this, который присваивается переменной self. Пока все это имеет смысл с точки зрения javascript. Но когда я выполняю этот код, я получаю сообщение об ошибке: Cannot read property 'doSomething' в строке self.doSomething() внутри функции processOneItem. Как может быть потеряна ссылка на объект this? Я что-то здесь делаю не так?

Я заметил одну вещь: если я заключу функцию iteratee внутрь другой безымянной функции, она, похоже, будет работать отлично. когда строка

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

заменяется строкой

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

, ссылка на объект this сохраняется внутри функции iteratee, и код работает отлично.

1 Ответ

1 голос
/ 13 августа 2020

Это вопрос javascript привязки. Вот упрощение вашего случая, приведенного выше, который дает один и тот же результат независимо от bluebird.

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

this из javascript для выполнения функции привязывается при выполнении этой функции.

  1. Элемент списка

Таким образом, при вызове

obj.a()

он проверяет правила this и обнаруживает, что он был вызван для объекта, поэтому this будет быть obj.

При вызове
c_function(obj.a)

c_function получает функцию в качестве аргумента. Когда он выполняет функцию, он пытается привязать this. Что касается области c_function, функция не вызывается для объекта. Поэтому он свяжет это в соответствии с правилами. Здесь c_function привязан к global this, и при выполнении paramsFunction() привязка будет такой же.

При звонке
c_function(function() { obj.a()}) // Or
c_function(() => { obj.a()})

Все знания 2-х предыдущих объединены. Важный вывод заключается в том, что в момент вызова, который вы вызываете, из obj

EXTRA Обратите внимание, что функция карты может вызывать вашу функцию с помощью bind, call или apply. Это повлияет на объем выполняемой функции.

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

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

Мне не удалось его найти, но через несколько лет go Кайл Симпсон прошел курс или семинар, на котором подробно объяснялась привязка области в javascript и всех его причудах.

...