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();.
Related
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.
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 need to somehow loop over the work array passed to _start then
for each of the items in the array, I need to somehow call the corresponding function with the same name.
I don't have control over the number of items in work the array or the number of items, I do know that there will always be a corresponding function.
I don't want to call all the functions at the same time, once the first function resolves after 3 seconds, I then want to call the second function, once the second function resolves after 3 seconds I then want to call the third function. Once the third function resolves after another 3 seconds I want to call _done().
In this example each function takes 3 seconds to complete _done wont gete called for 9 seconds.
function _start(data){
// Insert some kinda native magic loop
}
function _one(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 3000);
})
};
function _two(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(2);
}, 3000);
})
};
function _done(){
console.log('All done in 9 seconds)
}
(function(){
var work = ['_one', '_two', '_two'];
_start(work);
})();
Given the order is dicated by the array, you can use reduce to aggregate the promises into a chain
const _start = (...actions) => {
return actions.reduce((chain, action) => {
const func = this[action];
return chain.then(() => func());
}, Promise.resolve());
}
...
_start('_one', '_two', '_three').then(() => console.log('All done'));
See it in action - the example appends an extra then to the chain just to output any results from the promises (probably outwith the scope of this question but something you may have to consider if getting data back is required).
Update
Can see you intend on invoking _start from a different context in which the functions are declared, this is fine but you need to make sure you set the correct context before hand i.e.
const self = this;
(function() {
_start.bind(self)('_one', '_two', '_two');
})();
A function which creates a promise which sleeps:
const sleep = n => () => new Promise(resolve => setTimeout(resolve, n));
A function which sleeps after some input promise:
const sleepAfter = n => p => p.then(sleep(n));
A function which chains a bunch of promises, represented by functions:
const chain = (...promises) => promises.reduce((ret, promise) => ret.then(promise),
Promise.resolve());
Run a bunch of functions yielding promises, sleeping in between:
const _start = promises => chain(promises.map(sleepAfter(3000)));
Now just:
_start(_one, _two, _three).then(_done);
Try using this:
_one().then((firstResponse) {
return_two();
}) .then((secondResponse) => {
*second and first respone are already done*
});
Use promises then
_one().then((responseOne) => {
return _two();
}).then((responseTwo) => {
// _one & _two are done
});
I have a save function which behaves asynchronously, thus I'm using promises. A main save event will come in and gets certain then and catch handler attached to them once and only once. For my code example, that will happen in the importantSave method. Now there may be any number of other save calls that get called later, and you can refer to the sideSave method.
Is there a way to make the then and catch handlers in the importantSave method wait for all of the side saves to resolve/reject before they are called, even though they are set up first?
For example, I have the following code:
class Adapter {
promise;
next_attempt;
attempt() {
if ( this.promise && this.promise.isPending() ) {
this.next_attempt = this.attempt.bind( this );
return this.promise.then( ()=> {
if ( this.next_attempt ) {
return this.next_attempt();
}
} );
} else {
this.promise = new Promise( resolve=> {
setTimeout( resolve, 3000 );
} );
return this.promise;
}
}
}
const adapter = new Adapter();
function importantSave() {
adapter.attempt()
.then( ()=> {
console.log( 'Hello from the important save!' );
} );
}
function sideSave() {
adapter.attempt()
.then( ()=> {
console.log( 'Hello from the side save!' );
} );
}
// the important save which sets up its then and catches, but wants to be called after all have finished
importantSave();
// any number of side saves, but will only be called with the last side save
sideSave();
sideSave();
sideSave();
Is there a way to change this so that if the then in importantSave is actually ran after any thens in the sideSave calls? My real world example will call the save function 3 times with a delay in between each call and will only reject if all 3 fail. If another call to save happens before all 3 fail, I want the newest call to be the "next_attempt", overwriting any other attempts to save while the first one was still pending. If the original call failed to save, but one of the "sideSave" calls passed, I want the importantSave to be fulfilled, thus fall into the then, not the catch that the original one failed.
The way that this is commonly done, is with Promise.all.
Add all the promises you need to resolve in an array and pass it as argument:
Promise.all([importantSave(), sideSave(), sideSave(), sideSave()]).then(_ => {
console.log('all done');
});
You can of course build the array dynamically:
var arr = [importantSave()];
arr.push(sideSave());
arr.push(sideSave());
arr.push(sideSave());
Promise.all(arr).then(_ => {
console.log('all done');
});
If you need just one to fulfill, then use the opposite of those promises. You could use a helper function for that:
const invert = p => p.then(resp => { throw resp }).catch(err => err);
This function returns a promise that will reject when its argument fulfills, and vice versa.
Now you can do:
var arr = [importantSave()];
arr.push(sideSave());
arr.push(sideSave());
arr.push(sideSave());
invert(Promise.all(arr.map(invert))).then(_ => {
console.log('one done');
});