I'm quite new to using promises in JS, and im trying to have a function execute before executing some more code in another function. Only issue is that the promised function uses an if statement to loop a setTimeout command. I added an if statement to make sure the function is done looping before i resolve the promise but the resolve just isn't doing anything. I used console.log to make sure that the if statement is executing and it has no problems printing to the console on either side of the resolve. Any help would be greatly appreciated.
Code:
async makeToast(loader, toaster){
toaster.texture = loader.resources['toaster_down'].texture;
this.interactive = false;
this.x = toaster.x;
this.y = toaster.y - 100;
let transform = {y: this.y};
let popDown = new TWEEN.Tween(transform)
.to({y: toaster.y - 50}, 200)
.onUpdate(() => this.y = transform.y);
popDown.start();
await this.changeTexture(loader, toaster.setting)
console.log('toasting done');
this.interactive = true;
}
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) this.changeTexture(loader, setting);
else if(this.state == setting) resolve();
}, 1000);
});
}
After the first setTimeout callback executes, you will never resolve the outermost call's returned promise. You will resolve the innermost call's returned promise eventually, but that does nothing since the promise returned from there is never used.
You could write if (this.state < setting) resolve(this.changeTexture(loader, setting)) but I'd recommend a different, far less confusing (and non-recursive) way instead:
// This could be defined globally, can be useful elsewhere too
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// This is in your object
async changeTexture (loader, setting) {
while (this.state < setting) {
await delay(1000)
this.state++
this.texture = loader.resources[`bread${this.state}`].texture
}
}
Here I've made the changeTexture function async as well, so we can use await inside and therefore implement the delay in a more straight-forward manner and can build a regular while loop around the whole thing.
(Note: Technically your existing code does the first iteration unconditionally, so a do ... while would be more accurate, but I'm assuming that is just a result of the way you tried building it with setTimeout and not really what you need.)
You can as long as there is a closure linking the resolve variable in the Promise constructor with the resolve() you call in your if statement. However in your code you don't have this:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting)
this.changeTexture(loader, setting); <----------.
else if(this.state == setting) |
resolve(); <-- There is a closure to this /
}, 1000); /
}); .---------------------------'
} |
} However this function call will have it's own
"resolve" variable that is no longer captured
by this closure.
This means that when the if/else finally calls resolve() that resolve has nothing to do with the Promise you returned when you call changeTexture().
The way to do what you want is to not call changeTexture recursively so that you maintain a closure between the Promise's resolve variable and the resolve you finally call. To do this simply separate your setTimeout callback from the main changeTexture function:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
let loop = () => { // use arrow function to capture "this"
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) setTimeout(loop, 1000);
else if(this.state == setting) resolve();
}
loop();
});
}
}
Alternatively for minimal change in code you can get your code working by changing only one line:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) setTimeout(arguments.callee(),1000); // <----THIS
else if(this.state == setting) resolve();
}, 1000);
});
}
}
The arguments.callee variable points to the () => {... function you pass to setTimeout. However, arguments.callee is deprecated and is disabled in strict mode so use the loop function above if possible.
You can call resolve and reject from anywhere in your code you wish. But you must call exactly one of them exactly once from within your Promise.
Your sample code doesn't do that when your if-condition is false, so you need to fix that.
Related
I want to send data within a loop. Each turn in the loop has to wait for the answer. I tried to work with Promise. The first turn in the loop works, but the problem is, that the loop stops in the first turn after the promise. Would be happy for some explanation. Thanks!
let dataReceived;
for (let i = 0; i < arr.length; i++){
dataReceived = false;
sendInfo(arr[i]); // send some data
await checkDataReceived();
console.log(i);
}
function checkDataReceived() {
return new Promise((resolve, reject) => {
if(dataReceived == false) {
setTimeout(checkDataReceived, 100);
} else {
console.log('continue');
// do something...
resolve (true);
}
});
}
function awaitData(data){
// do something
dataReceived = true;
}
The idea behind the snippet you have provided is to poll for a state change of a variable (dataReceived) and take action when succeeded. In proper patterns, the programs should try to rely on events - especially in Node JS.
Having said that, the problem you are facing is the result of the creation of new Promise objects repeatedly and never resolving them.
The checkDataReceived function creates a new Promise (by calling itself) every time the polling gets a false result. The original promise it returns is never resolved. So, the calling await statement will never succeed in the for loop.
Instead of calling itself, the checkDataReceived function should try to resolve the original promise when the polling gets a truthy result.
function checkDataReceived() {
return new Promise((resolve, reject) => {
setInterval() => {if (dataReceived) resolve()}, 100)
});
}
Problem is in checkDataReceived() function you are only using resolve(true) in else block you need to either use reject or resolve in if block as well in order to return from promise
function checkDataReceived() {
return new Promise((resolve, reject) => {
if(dataReceived == false) {
setTimeout(checkDataReceived, 100);
//use either resolve or reject here
} else {
console.log('continue');
// do something...
resolve (true);
}
});
}
I'm attempting to define a function that returns a promise. The promise should resolve when a given array is set (push()).
To do this I'm attempting to use a Proxy object (influenced by this):
let a = []
;(async function(){
const observe = array => new Promise(resolve =>
new Proxy(array, {
set(array, key, val) {
array[key] = val;
resolve();
}
}));
while(true){
await observe(a);
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})(a);
;(async function(){
await new Promise(resolve => timerID = setTimeout(resolve, 2000))
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ')
})(a)
I can't see why this doesn't work. Does anyone have any idea?
More generally, what is a good way to have a promise resolve on push to an array?
The problems with your attempt:
you invoke .push on the original array, not the proxied one. Where you create the proxy, it is returned to no-one: any reference to it is lost (and will be garbage collected).
The code following after the line with await will execute asynchronously, so after all of your push calls have already executed. That means that console.log will execute when the array already has two elements. Promises are thus not the right tool for what you want, as the resolution of a promise can only be acted upon when all other synchronous code has run to completion. To get notifications during the execution synchronously, you need a synchronous solution, while promises are based on asynchronous execution.
Just to complete the answer, I provide here a simple synchronous callback solution:
function observed(array, cb) {
return new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) cb(); // now it is synchronous
return true;
}
});
}
let a = observed([], () =>
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:", `${a.pop()}`)
);
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ');
As noted before: promises are not the right tool when you need synchronous code execution.
When each push is executed asynchronously
You can use promises, if you are sure that each push happens in a separate task, where the promise job queue is processed in between every pair of push calls.
For instance, if you make each push call as part of an input event handler, or as the callback for a setTimeout timer, then it is possible:
function observed(array) {
let resolve = () => null; // dummy
let proxy = new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) resolve();
return true;
}
});
proxy.observe = () => new Promise(r => resolve = r);
return proxy;
}
let a = observed([]);
(async () => {
while (true) {
await a.observe();
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})();
setTimeout(() => a.push('ʕ·͡ᴥ·ʔ'), 100);
setTimeout(() => a.push('¯\(°_o)/¯ '), 100);
This is a simple version of what I'm trying to do in my application. I have an if statement which evaluates the result of a function call and then populates an array if the statement comes back as true. AFTER the if statement is completely finished, I want to run some more code such as the console.log as seen below.
I understand that the if's evaluation is taking too long to finish and javascript just continues to the console.log because of its asynchronicity. How do I make the code wait for the if statement to complete?
var tabs = [];
if (isTrue()) {
tabs.push('some string');
}
console.log(tabs[1]);
function isTrue() {
setTimeout(function() {
return true;
}, 500)
}
You can just wrap your code in a Promise and consume the returned values by calling then on it:
var tabs = [];
isTrue().then(res => {
if (res) {
tabs.push('some string');
}
return tabs;
}).then(arr => {
console.log(arr);
});
function isTrue() {
//Just wrap your existing code in a Promise constructor
return new Promise((resolve, reject) => {
setTimeout(() => {
//Pass whatever value you want to consume later to resolve
resolve(true);
}, 500)
});
}
You could pass a callback to the isTrue() function, something like:
function isTrue(_callback) {
setTimeout(function() {
// code here
// Call the callback when done
if (typeof(_callback) === 'function')
_callback(tabs);
});
}
function showTabs(tabs) {
console.log(tabs[1]);
}
isTrue(showTabs);
Ought to work.
Using modern javascript, you can achieve that using promises and async/await:
const isTrue = () => new Promise(resolve => setTimeout(resolve, 500, true));
// you can only use `await` inside an `async` function
async function main() {
// better use `let` instead of `var` since `let` is block scoped,
// see:
// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let>
let tabs = [];
if (await isTrue()) {
tabs.push('some string');
}
// array's index start by 0, not 1
console.log(tabs[0]);
}
main();
(this code also use arrow functions for isTrue.)
isTrue() returns undefined. The return true inside of the setTimeout callback will return back to the timeout call, not to the isTrue() call. The code executes immeadiately and there is no asynchronity involved (except for that timer that does nothing).
Suppose I have a function that executes an asynchronous action (doStuffAsync()) and then intends to do some other stuff (doOtherStuff()).
doStuffAsync() returns a Promise.
Also assume everything is mockable.
How do I test that my function awaits doStuffAsync() before trying to doOtherStuff()?
I thought of mocking doStuffAsync() using resolve => setTimeout(resolve(), timeout), but timeout-based testing looks very fragile.
You need a flag accessible by both doStuffAsync and doOtherStuff.
In doStuffAsync() write in that flag
In doOtherStuff() read from that flag and determine if it was written
Something like:
var isAsyncRunning = false;
function doStuffAsync(){
isAsyncRunning = true;
new Promise(function(resolve, reject) {
setTimeout(()=>{
isAsyncRunning = false;
resolve(); //irrelevant in this exercise
}, 1000);
});
}
doStuffAsync();
function doOtherStuff(){
if(isAsyncRunning){
console.log("Async is running.");
} else {
console.log("Async is no longer running.");
};
}
doOtherStuff();
setTimeout(() => {
//calling doOtherStuff 2 seconds later..
doOtherStuff();
}, 2000);
I managed to complete it with a less ugly solution than setTimeout – setImmediate.
function testedFunction() {
await MyModule.doStuffAsync();
MyModule.doOtherStuff();
}
it('awaits the asynchronous stuff before doing anything else', () => {
// Mock doStuffAsync() so that the promise is resolved at the end
// of the event loop – which means, after the test.
// -
const doStuffAsyncMock = jest.fn();
const delayedPromise = new Promise<void>(resolve => setImmediate(resolve()));
doStuffAsyncMock.mockImplementation(() => delayedPromise);
const doOtherStuffMock = jest.fn();
MyModule.doStuffAsync = doStuffAsyncMock;
MyModule.doOtherStuffMock = doOtherStuffMock;
testedFunction();
expect(doOtherStuffMock).toHaveBeenCalledTimes(0);
}
setImmediate will put off the resolution of your promise to the end of the event loop, which is after your test completes.
So, your assert that doOtherStuff() was not invoked:
Will pass if there is an await inside the testedFunction
Will fail if there isn't.
I have two variables where certain values needs to be fulfilled and then call a function. One of the variables get set from an onComplete call after an js-animation is finished. The other is called once a video-file is completed preloading. My problem is that I don't know which one that will be called first. Therefore I would like to check so that both values are fulfilled with a promise.
They both get their values set in two different callback functions.
I have this but I don't understand how to use the callbacks with Promise.
// Callback 1:
function nextSlide(event){
finishedAnim = true;
};
// Callback 2:
function handleNextFileComplete(event) {
nextVideoEl.src = nextVideo;
nextfileLoaded = "complete";
};
Promise.all([
]).then(() => {
slider.next();
});
One solution would be using promises instead of control variables. Let's say you have a function to start the animation and other to start loading the video file.
function startAnimation() {
return new Promise((resolve, reject) => {
// Start the animation and pass the onAnimationCompleted callback
function onAnimationCompleted(event) {
resolve();
}
});
}
function startLoadingVideo() {
return new Promise((resolve, reject) => {
// Start loading the video and pass the onVideoLoaded callback
function onVideoLoaded(event) {
resolve();
}
});
}
Now you can call both functions and use the function Promise.all() to handle the promises. The calling method would be something like:
let animationPromise = startAnimation();
let videoPromise = startLoadingVideo();
Promise.all([animationPromise, videoPromise])
.then(() => slider.next());
I realised that this beacame kind of complex situation.
First I have timer with this code:
TweenMax.delayedCall(7.3, delayCallComplete);
function delayCallComplete(event){
finished = true;
};
Then I have preloading of video with this code:
function loadNextVideo() {
var preloadNext = new createjs.LoadQueue(true);
preloadNext.addEventListener("fileload", handleNextFileComplete);
preloadNext.loadFile(nextVideo);
};
function handleNextFileComplete(event) {
nextVideoEl.src = nextVideo;
nextfileLoaded = "complete";
};
I want to check that both of these cases has happened before calling a function containing slider.next();.