Как получить доступ к переменным вне области действия в Promise.then (аналогично замыканию) - PullRequest
0 голосов
/ 05 января 2019

Обдумав это, уверен, что есть элегантный способ сделать это, но не уверен, что.

Я бы хотел что-то вроде:

let x = 5;

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(x);
});

x = 3;
// Print out 5 after 2 seconds.

В принципе, при настройке, аналогичной приведенной выше, существует ли способ распечатать '5' независимо от того, изменяется ли значение x во время асинхронного тайм-аута? В моем случае было бы трудно просто передать x в resolve().

Ответы [ 2 ]

0 голосов
/ 05 января 2019

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

function promiseFactory(x){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(x); // value as passed to factory call
             resolve(x)
        }, 1000)
    });
}

let x = 5;
promiseFactory(x) // returns a promise which will always see x as 5
    .then(function(x){console.log(x)})

Небольшое предупреждение: это работает здесь, потому что x является целым числом, которое является примитивным типом, поэтому значение копируется. Вам придется передать клонированный объект, если вы используете ссылочный тип, такой как object / array

0 голосов
/ 05 января 2019

Вы можете передать его через IIFE :

let x = 5;

const p = (x => new Promise((resolve, reject) => {
//         ^ use it here
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(x);
}))(x);
//  ^ pass it here

x = 3;

Причина, по которой это работает, заключается в том, что мы создаем область с помощью нашей функции, которая связывает переменную x в качестве одного из аргументов с любым значением, передаваемым в IIFE.

Это позволяет нам связать глобальный x с чем-то другим, но x, ограниченный внутри IIFE, не затрагивается.

Поскольку мы используем одно и то же имя как во IIFE, так и за его пределами, внутренний x также затеняет внешнее.

Возможно, использование разных имен сделает вещи более читабельными:

let x = 5;

const p = (y => new Promise((resolve, reject) => {
//         ^ use it here under a different name
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(x);
//  ^ pass it here

x = 3;

Примечание: вышеприведенное работает, потому что мы имеем дело с примитивными значениями , которые в JavaScript являются неизменяемыми и, таким образом, при каждом переназначении создается новое значение.

var a = 'a'; 
var b = a; // this will bind `b` to the copy of value of `a` 
a = 'changed'; // this won't affect `b`
console.log(a, b); // 'changed', 'a'

Если бы мы имели дело с объектами, использование IIFE не сработало бы:

let x = { changed: false };

const p = (y => new Promise((resolve, reject) => {
//         ^ still points to the same object as x
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(x);

x.changed = true; // this will affect y as well

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

var a = { name: 'a' }; 
var b = a; // this will bind `b` to the value of `a` (not copy)
a.name = 'changed'; // this will also change `b`
console.log(a.name, b.name); // 'changed', 'changed'

Чтобы достичь того, что вам нужно с объектами, вам нужно будет подражать тому, что движок JS делает с примитивами, и клонировать объект при передаче его во IIFE:

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))({ ...x });
//  ^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone

Или используя Object.assign:

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(Object.assign({}, x));
//  ^^^^^^^^^^^^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone

Примечание: оба объекта распространение и Object.assign выполняют неглубокое клонирование. Для глубокого клонирования вы можете найти множество библиотек в NPM .

См .: Какой самый эффективный способ глубокого клонирования объекта в JavaScript?

В большинстве случаев это также может работать:

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(JSON.parse(JSON.stringify(x)));
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone

Примечание. Использование IIFE - только быстрый пример. Обычная функция будет работать так же хорошо (но все равно будет иметь те же проблемы для непримитивных значений):

let x = 5;

const p = createPromise(x);

x = 3;

function createPromise(y) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 2000);
  }).then(() => {
    console.log(y);
  })
}
...