Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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) => {
  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().

like image 280
theahura Avatar asked Jan 05 '19 06:01

theahura


People also ask

How do you access local variables outside the scope?

Local variables cannot be accessed outside the function declaration. Global variable and local variable can have same name without affecting each other.

What happen when a variable of reference data type in goes out of scope?

Nothing physical happens. A typical implementation will allocate enough space in the program stack to store all variables at the deepest level of block nesting in the current function. This space is typically allocated in the stack in one shot at the function startup and released back at the function exit.

What does resolve () do in a Promise?

resolve() method "resolves" a given value to a Promise . If the value is a promise, that promise is returned; if the value is a thenable, Promise. resolve() will call the then() method with two callbacks it prepared; otherwise the returned promise will be fulfilled with the value.

How do you resolve promises and get data?

Consider the following code snippet: let primise = new Promise((resolve, reject) => { resolve({ x: 10 }); }); setTimeout(() => { // At some moment in the future (Promise is resolved) console. log(promise); }, 200); Now, the promise was resolved with { x: 10 } .


1 Answers

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 bind `b` to the copy of value of `a` 
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 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'

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?

For most cases, this could also work:

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

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);
  })
}
like image 93
nem035 Avatar answered Nov 08 '22 20:11

nem035