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()
.
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()
.
- You could avoid such issues by not relying on global state in the first place. – user10675354 Commented Jan 7, 2019 at 9:27
2 Answers
Reset to default 9You 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);
})
}
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