How can I delay a chain of promises? I need this because I want to wait for a CSS animation to complete before going on with the script.
The purpose of the function is to open a view. If the view is not already open, then open it (by changing a class), wait for the css animation, go on. If the view is already open, do nothing and go on.
I want to call the function like this:
(It is a function within an angular controller)
$scope.openView(viewId).then(function() {
$scope.openAnotherView(anotherViewId);
});
/** Function to open some view **/
$scope.openView = function (viewId) {
function timeout(delay) {
return new Promise(function(resolve, reject) {
$timeout(resolve, delay);
});
}
// Check if view is already open
if ($scope.viewId != viewId) {
$scope.viewId = viewId;
// get data from ajaxcall (also a promise)
return MyService.getData(viewId).then(function(data) {
// add data to view
// change class to open view
// this is working ok!
}).then(function() {
return timeout(30000 /* some large number (testing purpose) */ )
});
} else {
// view is already open, so return here we don't have to wait
// return empty promise, with no timeout
return new Promise(function(resolve, reject) {
resolve()
});
}
}
This code works, but the delay is not working. Is my approach ok? What am I missing here?
Edit 1: improved the code with the suggestion from #sdgluck
Edit 2: Some clarification of the main question:
To clarify the main question a bit more:
Can I use this construction in my code?
// code doesnt know wheter to wait or not
// can the Promise do this?
openView().then(function() {
openAnotherView();
}
Outcome 1:
the browser will call openView() but since it is already open it will just call openAnotherView() right away (no delay).
Outcome 2 :
The view is not open, so openView() will open it, then a delay (or as #Dominic Tobias points out, add an eventlister?) then call openAnotherView() after some delay.
Thanks for any help!
Edit 3: Added a fiddle with the problem explained
http://jsfiddle.net/C3TVg/60/
To delay a promise, simply call the resolve function after a wait time.
new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 3000); // Wait 3s then resolve.
});
The issue with your code is that you are returning a Promise and then inside the then of that Promise you are creating another one and expecting the original promise to wait for it - I'm afraid that's not how promises work. You would have to do all your waiting inside the promise function and then call resolve:
Edit: This is not true, you can delay the promise chain in any then:
function promise1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('promise1');
resolve();
}, 1000);
})
.then(promise2);
}
function promise2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('promise2');
resolve();
}, 1000);
});
}
function promise3() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('promise3');
resolve();
}, 1000);
});
}
promise1()
.then(promise3)
.then(() => {
console.log('...finished');
})
However that is not a good way to wait for a css animation. It's better to listen to the transitionend event:
element.addEventListener('transitionend', onTransitionEnd);
element.classList.add('transition-me');
Note if you're using an animation instead of a transition the same concept applies but use the animationend event.
Each then accepts a function which should return a Promise. It does not accept an instance of Promise. You want to return the call to timeout:
return MyService
.getData(viewId)
.then(function(data) {
// ...
})
.then(function () {
return timeout(3000);
});
Alternatively, have timeout return a function instead of a Promise:
function timeout(delay) {
return function () {
return new Promise(function(resolve, reject) {
// ^^^^^^^ (misspelt in your example)
$timeout(resolve, delay);
});
};
}
And then you can use it as in your example:
return MyService
.getData(viewId)
.then(function(data) {
// ...
})
.then(timeout(3000));
How can I delay a chain of promises?
$timeout returns a promise. Return that promise to chain it.
$scope.openView = function (viewId) {
// Check if view is already open
if ($scope.viewId == viewId) {
//chain right away with empty promise
return $q.when();
};
//otherwise if view is not already open
var p = MyService.getData(viewId).then(function(data) {
// add data to view
// change class to open view
// this is working ok!
});
var pDelayed = p.then (function () {
//return to chain delay
return $timeout(angular.noop, 30000);
});
//return delayed promise for chaining
return pDelayed;
};
$scope.openView(viewId).then(function() {
//use chained promise
$scope.openAnotherView(anotherViewId);
});
Related
What's the difference between the two (without a return)
function doAsyncTask() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Async Work Complete");
if (error) {
reject();
} else {
resolve();
}
}, 1000);
});
return promise;
}
The following has no "Return Promise"
function doAsyncTask() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Async Work Complete");
if (error) {
reject();
} else {
resolve();
}
}, 1000);
});
}
As an extension to Quentin's answer, you should return a promise always.
Idea is, if a function is an async function, it should provide a way to listen to changes. Its upon caller to decide if they need to react to changes.
So you can call your function as:
doAsyncTask().then(...);
or just
doAsyncTask();
But if we do not return promise, caller will never have an option to llisten.
ES6 why does one have to return a promise?
You don't.
What's the difference between the two (without a return)
The difference is that one doesn't return anything.
(So you can't call doAsyncTask and make use of the return value).
var doAsyncTask1 = function() {
var promise = new Promise(resolve => {
/// task that takes 5 seconds
setTimeout(resolve, 5000);
});
return promise;
}
var doAsyncTask2 = function() {
var promise = new Promise(resolve => {
/// task that takes 5 seconds
setTimeout(resolve, 5000);
});
// no return
}
await doAsyncTask1();
console.log('task complete'); // this line of code runs 5 seconds after the previous one
await doAsyncTask2(); // because you have returned nothing, 'await' does nothing
console.log('task2 not complete yet'); // this line of code runs immediately after the previous one, before the 5-second task is complete
I have a function called "test_sheet" that is supposed to return a value. that value will then be passed to a tester function which will tell me if I passed or failed the test.
inside my "test_sheet" I have a few async operations which are handled by promises.
now, how can I return a (non-promise) value from my test_sheet function.
function test_sheet()
{
//all my logic will go here
new Promise(function(resolve, reject)
{
//simulating an async operation
setTimeout(() => resolve(true), 1000);
})
.then(function(res){return res});
}
function tester()
{
//not allowed to change this function in any way
if(test_sheet() == true)
console.log("pass!");
else
console.log("fail!");
}
tester();
Is there any better way of doing this?
Well, technically it is possible, tester() may reamain intact:
var test_sheet=false;
function start_test()
{
//all my logic will go here
new Promise(function(resolve, reject)
{
//simulating an async operation
setTimeout(() => resolve(true), 1000);
})
.then(res => {
test_sheet=true;
tester();
});
}
function tester()
{
//not allowed to change this function in any way
test_sheet == true ? console.log("pass!") : console.log("fail!");
}
//tester();
start_test();
But the test starts with start_test() now, and test_sheet became a variable, with the sole purpose of acting as an argument - which could not be added to testing() without modifying it.
A nonworking bad design is transformed to working bad desing this way.
test_sheet() always returns a promise so try to get it resolved using async await or .then which feeds into the tester() function.
call you function this way:
test_sheet().then(function(test_sheet){
tester(test_sheet)})
for this you need to pass the boolean return value from test_sheet() to tester(test_sheet)
If you handle asynchronous code you have to use promise or callback and handle with async/await to change them to synchronous code
For example
function test_sheet()
{
//all my logic will go here
return new Promise(function(resolve, reject) {
//simulating an async operation
setTimeout(() => resolve(true), 2000);
})
}
async function tester()
{
//not allowed to change this function in any way
await test_sheet() == true ? console.log("pass!") : console.log("fail!");
}
tester();
Although I do realize what the error means and why it happens, I think I have a use case that goes outside the expected. I am using Word.run() inside another promise, like so:
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
resolve(someData);
});
});
So, if I understood it correctly, this resolves my promise, but leaves the .run method hanging since there's no return context.sync() at the end? Or did I get it wrong? If I'm right, how can I rewrite the example above to keep .run working properly?
If you want your promise to resolve after the context syncs...
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
return context.sync().then(function() {
resolve(someData); // promise will resolve after sync resolves
});
});
});
If you don't need to resolve after but just want to sync the context at some point in the future, you can actually do this and it won't wait for the sync:
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
resolve(someData); // this will resolve the promise
return context.sync(); // this will actually still happen since the function never returns
});
});
I use the following code and there is something that a bit confuse me
if I put in the timeout 1000ms I see that the promise called in the right order
but if I change it to the following
This is step 3
This is step 2
This is step 1
I guess that this happen since when the function is resolved then he proceed to the next function am I correct ? if this is true how can I do this chain that when the first is finished then he proceed to the second etc but without using the promise hell :-)
https://jsfiddle.net/2yym400j/
var step1 = function(ms) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("This is step 1");
resolve();
}, ms);
})
}
var step2 = function(ms) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("This is step 2");
resolve();
}, ms);
})
};
var step3 = function(ms) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("This is step 3");
resolve();
}, ms);
})
};
step1(500)
.then(step2(300))
.then(step3(200))
.catch(function(err) {
console.log(err);
});
Just pass a function instead of the result of the steps.
step1(500)
.then(function() { return step2(300); })
.then(function() { return step3(200); })
.catch(function(err) {
console.log(err);
});
Without this, you are just calling each step without "blocking" for the previous step to resolve.
I know you've already gotten an answer as to why your original code didn't work, but I thought I'd show another way to approach it that is a bit more DRY than what you were doing. You can create a single function that returns a function and then you can use that in the fashion you were using because it returns a function that will be called later which is what .then() wants. So, you can do something like this:
function delay(t, msg) {
return function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(msg);
resolve();
}, t);
});
}
}
delay(500, "This is step 1")()
.then(delay(300,"This is step 2"))
.then(delay(200, "This is step 3"))
.catch(function(err) {
console.log(err);
});
Working demo here: https://jsfiddle.net/jfriend00/mbpq4g8m/
Daniel White's comment and answer are correct, but I thought an additional explanation might help.
Your original code broke two rules:
then can't take a Promise, but it can take a function that returns a Promise. If you pass in anything except a function, it will be ignored, just like you passed in null instead.
As soon as you call new Promise(someFunction), the someFunction executes asynchronously without waiting for anything. This means you can call new Promise from within a then function, but if you call it early then it won't have the delayed behavior you're expecting.
So given that each call to stepN returns a newly-constructed promise, I've inlined everything that happens synchronously and renamed the results constructAndReturnPromiseN and constructedPromiseThatStepNReturns. That makes your original code looks like this so all the promises happen at once:
constructedPromise1ThatStep1Returns
.then(constructedPromise2ThatStep2Returns) // BAD: Don't pass promises into then.
.then(constructedPromise3ThatStep3Returns) // BAD: Don't pass promises into then.
.catch(function(err) {
console.log(err);
});
...where Daniel White's code does this:
constructedPromise1ThatStep1Returns
// GOOD: The then function returns a promise that is *constructed inside* the function.
.then(function() { return constructAndReturnPromise2(300); })
.then(function() { return constructAndReturnPromise3(200); })
.catch(function(err) {
console.log(err);
});
I am really hoping someone can help me with this issue despite my inability (confidentiality issues) to share too much information. Basically, I wrote this function to retry failed promises:
function retryOnFailure(func) {
return func().then(function(resp) {
return resp
}, function(err) {
return setTimeout(function() {
return retryOnFailure(func)
}, 5000)
}
)}
And then I have this code to utilize it:
function getStuff() {
return $.get('/some/endpoint')
}
retryOnFailure(getStuff).then(function(res) { console.log(res) })
Now the recursive retry function works fine, but my then in the last line of code doesn't fire in the event that an error case is reached in retryOnFailure. I know that the reason is because each recursive call creates a new promise so the promise that I'm listening to in my last line is not the same one that gets resolved, but I'm having difficulty figuring out how to remedy this. I want this function to be a generic retry function and as such don't want to handle the actual success in the method, but I'm not seeing any other way. Any help would be greatly appreciated!
return setTimeout(function() {
return retryOnFailure(func)
}, 5000)
looks like you're trying to do the right thing - return the next result from your .catch callback. However, setTimeout does not return a promise, so retryOnFailure() will be resolved immediately (with the timeout cancellation id).
Instead, you need to make sure that for all asynchronous functions that you use a promise is produced, so let's start by promisifying setTimeout:
function delay(ms) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, ms);
});
}
Now we can use that to chain the retry after a delay, and get a promise from it:
function retryOnFailure(func) {
return func().then(null, function(err) {
return delay(5000).then(function() {
return retryOnFailure(func);
});
});
}
Notice that instead of .then(null, …) you can also use .catch(…).
This is the first way that came to mind, there is probably a more elegant solution:
function retryOnFailure(func, onSuccess) {
return func().then(function(resp) {
onSuccess(resp);
}, function(err) {
return setTimeout(function() {
return retryOnFailure(func, onSuccess)
}, 5000)
}
)}
retryOnFailure(getStuff, function(res) { console.log(res) });
The problem is your call to setTimeout, it breaks the flow of your promise. Try something like this instead:
function retryOnFailure(func) {
return new Promise(function(resolve) {
func().then(function(resp) {
resolve(resp);
}).catch(function(err) {
setTimeout(function() {
retryOnFailure(func).then(resolve);
}, 5000)
});
});
};
retryOnFailure(getStuff).then(function(res) { console.log(res) });
Note how I've created a new promise inside the function which never throws an error, but instead will simply attempt to process the func() and recurse itself on failure.
It also may be a good idea to put some kind of retry-limit as well.
Good luck!