How to access out of scope variables in Promise.then (similar to closure)

问题: Stumped on this, sure there is an elegant way to do this but not sure what. I would like something like: let x = 5; const p = new Promise((resolve, reject) => {...

问题:

Stumped on this, sure there is an elegant way to do this but not sure what.

I would like something like:

let x = 5;

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

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

Basically, given a setup similar to above, is there a way to print out '5' regardless of whether the value of x is changed during the async timeout? In my case, it would be hard to simply pass x in the resolve().


回答1:

You can pass it via an 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;

The reason this works is because we are creating a scope via our function which is binding a variable x as one of its arguments to whatever value is passed into the IIFE.

This allows us to bind the global x to something else but the x bounded within the IIFE is unaffected.

Since we're using the same name both within the IIFE and outside of it, the inner x is also shadowing the outer one.

Maybe using different names would make things more readable:

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;


Note: the above works because we're dealing with primitive values, which in JavaScript are immutable and thus a new one is re-created on each re-assignment.

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

If we were dealing with objects, using an IIFE wouldn't work:

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

The reason is that objects aren't immutable and thus each bound variable is pointing to the same object.

var a = { name: 'a' }; 
var b = a; // this will bound b to the value and will not copy the object a is pointing to
a.name = 'changed'; // this will also change b
console.log(a.name, b.name); // 'changed', 'changed'

In order to achieve what you need with objects, you'll have to mimic what the JS engine does with primitives and clone the object when passing it into the 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

Or using 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

Note: Both object spread and Object.assign perform a shallow clone. For deep cloning, you can find many libraries on NPM.

See: What is the most efficient way to deep clone an object in JavaScript?


Note: Using an IIFE is just a quick example. A regular function would work just as well (but still have the same issues for non-primitive values):

let x = 5;

const p = createPromise(x);

x = 3;

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


回答2:

Yes, you can use a factory function to generate your promise which can act as a closure for your variable.

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

A small caveat: this works here because x is an integer which is a primitive type so the value gets copied over. You'll have to pass a cloned object if you're using a reference type like object/array

  • 发表于 2019-01-07 02:55
  • 阅读 ( 176 )
  • 分类:网络文章

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除