I'm putting together a promise chain, new to this so apologies. What I want to do is, for any errors in any particular function, I want to wait a second and try again until it succeeds, then continue with the promise chain as per the original plan.
I tried an if statement that called the function again if there was an error message and resolved if there was not, but this would not continue with the promise chain on a success.
I then worked a solution that included a for loop built into the function, as below:
var firstMethod = function() {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('first method completed');
resolve();
}, 1000);
});
return promise;
};
var secondMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
for(let randomNumber ; randomNumber < .9||randomNumber == null ; )
{
setTimeout(function() {
randomNumber = Math.random();
console.log('the number wasn\'t big enough');
if (randomNumber>.9)
{
console.log("got a number big enough");
resolve();
}
}, 1000)
}
});
return promise;
};
var thirdMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('third method completed');
resolve();
}, 1000);
});
return promise;
};
firstMethod()
.then(secondMethod)
.then(thirdMethod);
It completes firstMethod and then hangs when I try to run it. If I comment out the setTimeout in the secondMethod it works, but I feel this is not emulating the real case scenario I want to use this for.
Where am I going wrong? Is there a better way to loop mid promise chain until you achieve a particular result, then continue with the promise chain?
You can replace the secondMethod with
var secondMethod = function (someStuff) {
var promise = new Promise(function (resolve, reject) {
let randomNumber;
(function doItUntilGetEnoughBigNumber() {
setTimeout(function () {
randomNumber = Math.random();
if (randomNumber > .9) {
console.log("got a number big enough", randomNumber);
resolve();
} else { // edited: message moved her
console.log('the number wasn\'t big enough', randomNumber);
doItUntilGetEnoughBigNumber();
}
}, 1000)
})()
});
return promise;
};
The reason your code doesn't work, because the condition to stop for loop is in asynchronous, so it will be deferred, so the for loop will go forever.
Update Without using IIFE
var secondMethod = function (someStuff) {
var promise = new Promise(function (resolve, reject) {
let randomNumber;
function doItUntilGetEnoughBigNumber() {
setTimeout(function () {
randomNumber = Math.random();
if (randomNumber > .9) {
console.log("got a number big enough", randomNumber);
resolve();
} else {
console.log('the number wasn\'t big enough', randomNumber);
doItUntilGetEnoughBigNumber();
}
}, 1000)
}
doItUntilGetEnoughBigNumber();
});
return promise;
};
The important thing about async events (e.g. when setTimeout finishes) is that the handler is not called directly, but the event is put into the so called event queue. The javascript engine (the event loop) takes one event after another out of it and executes it, when its done it takes the next one. At first it will execute your for loop and set all the timeouts. Then when its done and one second passed it will start executing the setTimeout handlers. But as your for loop is infinite, it will never reach this step.
The easiest would be to use the async syntax:
const timer = ms => start => new Promise(res => setTimeout(res, ms));
async function loop(){
while(true){
await timer(1000)();
if(Math.random() > 0.9) return;
}
}
timer(1000)()
.then(loop)
.then(timer(1000))
.then(() => console.log("done"));
Alternatively you could use a recursive async function:
async function loop(){
await timer(1000)();
if(Math.random() > 0.9)
return true;
else
return loop();
}
Related
This might seem a silly question, but I am a newbie in this topic. In the script below i have two promises. By now "secondPromise" executing first, and then executing "firstPromise". Cuz in the "secondPromise" i set less time. But how to execute "firstPromise" first, after finished that, start executing the "secondPromise"?
How to rewrite the script below
(async function()
{
//var final = new Array();
var final;
const firstPromise = new Promise(
function(resolve)
{
let result = 2 + 2;
resolve(result);
setTimeout(() => console.log("please show me first"), 2000);
});
const secondPromise = new Promise(
function(resolve)
{
let result2 = 0;
resolve(result2 + 1);
setTimeout(() => console.log("please show me second"), 1000);
});
var myP = Promise.all([firstPromise, secondPromise]).then((values) => {
return values[0]+values[1];
});
return myP;
})();
(async function()
{
//var final = new Array();
var final;
const firstPromise = new Promise(
function(resolve)
{
let result = 2 + 2;
resolve(result);
setTimeout(() => console.log("please show me first"), 2000);
});
const secondPromise = new Promise(
function(resolve)
{
let result2 = 0;
resolve(result2 + 1);
setTimeout(() => console.log("please show me second"), 1000);
});
var myP = Promise.all([firstPromise, secondPromise]).then((values) => {
return values[0]+values[1];
});
return myP;
})();
Quentin's answer is correct: the function you pass to new Promise happens immediately. However: because you already have this in an async function, you can await Promises within it. This pauses the async function until the Promise resolves, so unlike your function with Promise.all that waits for your explicit new Promise Promises in parallel, my function waits for those Promises serially.
Furthermore, if you want the new Promise constructions you wrote to wait for the action in setTimeout, you need to wait and call the resolve method within the callback that setTimeout calls, not outside them as you have it.
console.log("start");
(async function () {
const firstValue = await new Promise(
// ^^^^^
function (resolve) {
let result = 2 + 2;
setTimeout(() => {
console.log("please show me first");
resolve(result); // Promise resolves after 2000ms
}, 2000);
});
const secondValue = await new Promise(
// ^^^^^
function (resolve) {
let result2 = 0;
setTimeout(() => {
console.log("please show me second");
resolve(result2 + 1);
}, 1000);
});
// These are values, not promises, because you await them.
// However, the async function still returns a Promise, because
// "async" covers the asynchronicity of the Promises you await.
return firstValue + secondValue;
})().then(x => console.log(x)); // or .then(console.log)
You can't execute a promise.
You can execute a function.
If you pass a function to a Promise constructor, then it will be executed (by the Promise constructor) immediately.
There is no way to delay the execution of a function passed to a Promise constructor.
You could wrap your calls to new Promise in functions, and then only call the second function when the first promise is resolved.
(Note, however, that in your examples the calls to setTimeout happen after the resolve function is called).
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
Right now I have my promise wait 2 seconds then resolve the promise. I want my promise to check if the variable has data if it doesn't wait like half a second then check again if the variable has data. Is there a way to set that loop up efficiently? I am fairly new to promises.
My code:
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(real);
}, 2000);
})
Want it to be something like this. So I am waiting for the minimal amount of time
return new Promise((resolve, reject) => {
if(real == 3){
resolve(real);
}else{
setTimeout(function(){
//repeat promise check
}, 200);
}
})
You can usually find a different pattern that doesn't require polling values — using events or a callback where you set the value. But, if for some reason you must, you can keep polling in an interval. Just don't resolve the promise until you have satisfied your requirement. For example:
let real = 0
function waitForValue(){
return new Promise((resolve, reject) => {
if(real == 3){
resolve(real);
} else{
let interval = setInterval(() => {
console.log("checking again")
if(real == 3) {
console.log("value set")
resolve(real);
clearInterval(interval)
}
}, 500);
}
})
}
// set the value in 2 seconds
setTimeout(() => real = 3, 2000)
waitForValue().then(v => console.log("resolved value:", v))
You can also do this in an async function with a little more clarity:
let real = 0
let wait = t => new Promise(resolve => setTimeout(resolve, t))
async function waitForValue(){
while (real !== 3) {
console.log("checking")
await wait(500)
}
console.log("value set")
return real
}
setTimeout(() => real = 3, 2000)
waitForValue().then(console.log)
I ran into a bug in my code that puzzled me for a long time and am looking for some clarification.
In this code, the commented out inner promise was causing a problem. The Promise.all() at the end was continuing as soon as the setTimeout hit, not after the resolve inside the timeout.
Wrapping the async code with a promise fixes the flow problem, but why is this?
Essentially, why can't we just run normal async code in a .then() chain, an return a Promise.resolve() at the end of the async callback?
var asyncPromise = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Async Promise done');
resolve();
}, 1000);
});
};
var generateSignupPromises = function(qty) {
var promiseArray = [];
for (var i = 1; i <= qty; i++) {
promiseArray.push(
function() {
return asyncPromise()
.then(function() {
console.log('Before Timeout');
//Uncommenting this fixes the issue
//return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('After Timeout');
//resolve();
return Promise.resolve();
}, 500);
//})
});
}
);
}
return promiseArray;
};
var test = generateSignupPromises(1);
Promise.all([test[0]()])
.then(function() {
console.log('Done');
});
Link to running code: http://www.es6fiddle.net/imfdtuxc/
why can't we just run normal async code in a .then() chain, an return a Promise.resolve() at the end of the async callback?
You perfectly can. But any value - be it a promise or whatever - being returned from a plain asynchronous callback is just ignored as usual.
There is nothing that starting the asynchronous action inside a then callback changes about this, setTimeout just doesn't return a promise - and the then won't know about anything asynchronous happening that it could wait for.
If you want to return a promise from a callback and get another promise for that eventual result, it has to be a then callback:
asyncPromise()
.then(function() {
return new Promise(function(resolve, reject) {
// ^^^^^^
setTimeout(resolve, 500);
}).then(function() {
// ^^^^^^^^^^^^^^^
return Promise.resolve();
});
});
Then is a sync function so if you want to do async task inside then, you has to return a Promise.
Also, Promise.all expect an array of promises. Not an array of an array.
var asyncPromise = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Async Promise done');
resolve();
}, 1000);
});
};
var generateSignupPromises = function(qty) {
var promiseArray = [];
for (var i = 1; i <= qty; i++) {
promiseArray.push(
function() {
return asyncPromise()
.then(function() {
console.log('Before Timeout');
//Uncommenting this fixes the issue
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('After Timeout');
resolve();
//return Promise.resolve();
}, 500);
})
});
}
);
}
return promiseArray;
};
var test = generateSignupPromises(1);
Promise.all([test[0]()])
.then(function() {
console.log('Done');
});
http://www.es6fiddle.net/imfe2sze/
I'm playing around with promises and I'm having trouble with an asynchronous recursive promise.
The scenario is an athlete starts running the 100m, I need to periodically check to see if they have finished and once they have finished, print their time.
Edit to clarify :
In the real world the athlete is running on a server. startRunning involves making an ajax call to the server. checkIsFinished also involves making an ajax call to the server. The code below is an attempt to imitate that. The times and distances in the code are hardcoded in an attempt to keep things as simple as possible. Apologies for not being clearer.
End edit
I'd like to be able to write the following
startRunning()
.then(checkIsFinished)
.then(printTime)
.catch(handleError)
where
var intervalID;
var startRunning = function () {
var athlete = {
timeTaken: 0,
distanceTravelled: 0
};
var updateAthlete = function () {
athlete.distanceTravelled += 25;
athlete.timeTaken += 2.5;
console.log("updated athlete", athlete)
}
intervalID = setInterval(updateAthlete, 2500);
return new Promise(function (resolve, reject) {
setTimeout(resolve.bind(null, athlete), 2000);
})
};
var checkIsFinished = function (athlete) {
return new Promise(function (resolve, reject) {
if (athlete.distanceTravelled >= 100) {
clearInterval(intervalID);
console.log("finished");
resolve(athlete);
} else {
console.log("not finished yet, check again in a bit");
setTimeout(checkIsFinished.bind(null, athlete), 1000);
}
});
};
var printTime = function (athlete) {
console.log('printing time', athlete.timeTaken);
};
var handleError = function (e) { console.log(e); };
I can see that the promise that is created the first time checkIsFinished is never resolved. How can I ensure that that promise is resolved so that printTime is called?
Instead of
resolve(athlete);
I could do
Promise.resolve(athlete).then(printTime);
But I'd like to avoid that if possible, I'd really like to be able to write
startRunning()
.then(checkIsFinished)
.then(printTime)
.catch(handleError)
The bug is that you are passing a function that returns a promise to setTimeout. That promise is lost into the ether. A band-aid fix might be to recurse on the executor function:
var checkIsFinished = function (athlete) {
return new Promise(function executor(resolve) {
if (athlete.distanceTravelled >= 100) {
clearInterval(intervalID);
console.log("finished");
resolve(athlete);
} else {
console.log("not finished yet, check again in a bit");
setTimeout(executor.bind(null, resolve), 1000);
}
});
};
But meh. I think this is a great example of why one should avoid the promise-constructor anti-pattern (because mixing promise code and non-promise code inevitably leads to bugs like this).
Best practices I follow to avoid such bugs:
Only deal with async functions that return promises.
When one doesn't return a promise, wrap it with a promise constructor.
Wrap it as narrowly (with as little code) as possible.
Don't use the promise constructor for anything else.
After this, I find code easier to reason about and harder to bugger up, because everything follows the same pattern.
Applying this to your example got me here (I'm using es6 arrow functions for brevity. They work in Firefox and Chrome 45):
var console = { log: msg => div.innerHTML += msg + "<br>",
error: e => console.log(e +", "+ e.lineNumber) };
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var startRunning = () => {
var athlete = {
timeTaken: 0,
distanceTravelled: 0,
intervalID: setInterval(() => {
athlete.distanceTravelled += 25;
athlete.timeTaken += 2.5;
console.log("updated athlete ");
}, 2500)
};
return wait(2000).then(() => athlete);
};
var checkIsFinished = athlete => {
if (athlete.distanceTravelled < 100) {
console.log("not finished yet, check again in a bit");
return wait(1000).then(() => checkIsFinished(athlete));
}
clearInterval(athlete.intervalID);
console.log("finished");
return athlete;
};
startRunning()
.then(checkIsFinished)
.then(athlete => console.log('printing time: ' + athlete.timeTaken))
.catch(console.error);
<div id="div"></div>
Note that checkIsFinished returns either athlete or a promise. This is fine here because .then functions automatically promote return values from functions you pass in to promises. If you'll be calling checkIsFinished in other contexts, you might want to do the promotion yourself, using return Promise.resolve(athlete); instead of return athlete;.
Edit in response to comments from Amit:
For a non-recursive answer, replace the entire checkIsFinished function with this helper:
var waitUntil = (func, ms) => new Promise((resolve, reject) => {
var interval = setInterval(() => {
try { func() && resolve(clearInterval(interval)); } catch (e) { reject(e); }
}, ms);
});
and then do this:
var athlete;
startRunning()
.then(result => (athlete = result))
.then(() => waitUntil(() => athlete.distanceTravelled >= 100, 1000))
.then(() => {
console.log('finished. printing time: ' + athlete.timeTaken);
clearInterval(athlete.intervalID);
})
.catch(console.error);
var console = { log: msg => div.innerHTML += msg + "<br>",
error: e => console.log(e +", "+ e.lineNumber) };
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var waitUntil = (func, ms) => new Promise((resolve, reject) => {
var interval = setInterval(() => {
try { func() && resolve(clearInterval(interval)); } catch (e) { reject(e); }
}, ms);
});
var startRunning = () => {
var athlete = {
timeTaken: 0,
distanceTravelled: 0,
intervalID: setInterval(() => {
athlete.distanceTravelled += 25;
athlete.timeTaken += 2.5;
console.log("updated athlete ");
}, 2500)
};
return wait(2000).then(() => athlete);
};
var athlete;
startRunning()
.then(result => (athlete = result))
.then(() => waitUntil(() => athlete.distanceTravelled >= 100, 1000))
.then(() => {
console.log('finished. printing time: ' + athlete.timeTaken);
clearInterval(athlete.intervalID);
})
.catch(console.error);
<div id="div"></div>
Using setTimeout / setInterval is one of the scenrios that doesn't play well with promises, and causes you to use the frowned promise anti-pattern.
Having said that, if you reconstruct your function make it a "wait for completion" type of function (and name it accordingly as well), you'd be able to solve your problem. The waitForFinish function is only called once, and returns a single promise (albeit a new one, on top of the original promise created in startRunning). The handling of the recurrence through setTimeout is done in an internal polling function, where proper try/catch is used to ensure exceptions are propagated to the promise.
var intervalID;
var startRunning = function () {
var athlete = {
timeTaken: 0,
distanceTravelled: 0
};
var updateAthlete = function () {
athlete.distanceTravelled += 25;
athlete.timeTaken += 2.5;
console.log("updated athlete", athlete)
}
intervalID = setInterval(updateAthlete, 2500);
return new Promise(function (resolve, reject) {
setTimeout(resolve.bind(null, athlete), 2000);
})
};
var waitForFinish = function (athlete) {
return new Promise(function(resolve, reject) {
(function pollFinished() {
try{
if (athlete.distanceTravelled >= 100) {
clearInterval(intervalID);
console.log("finished");
resolve(athlete);
} else {
if(Date.now()%1000 < 250) { // This is here to show errors are cought
throw new Error('some error');
}
console.log("not finished yet, check again in a bit");
setTimeout(pollFinished, 1000);
}
}
catch(e) { // When an error is cought, the promise is properly rejected
// (Athlete will keep running though)
reject(e);
}
})();
});
};
var printTime = function (athlete) {
console.log('printing time', athlete.timeTaken);
};
var handleError = function (e) { console.log('Handling error:', e); };
startRunning()
.then(waitForFinish)
.then(printTime)
.catch(handleError);
While all this code is functioning properly, a polling solution is never advised in an asynchronous environment and should be avoided if possible. In your case, since this sample mocks communication with a server, I'd consider using web sockets if possible.
Since your use of promises is pretty off the mark, it's a little hard to tell exactly what you're trying to do or what implementation would best fit, but here's a recommendation.
Promises are a one-shot state machine. As such, you return a promise and exactly one time in the future, the promise can be either rejected with a reason or resolved with a value. Given that design of promises, I think what makes sense would be something that can be used like this:
startRunning(100).then(printTime, handleError);
You could implement that with code like this:
function startRunning(limit) {
return new Promise(function (resolve, reject) {
var timeStart = Date.now();
var athlete = {
timeTaken: 0,
distanceTravelled: 0
};
function updateAthlete() {
athlete.distanceTravelled += 25;
console.log("updated athlete", athlete)
if (athlete.distanceTravelled >= limit) {
clearInterval(intervalID);
athlete.timeTaken = Date.now() - timeStart;
resolve(athlete);
}
}
var intervalID = setInterval(updateAthlete, 2500);
});
}
function printTime(athlete) {
console.log('printing time', athlete.timeTaken);
}
function handleError(e) {
console.log(e);
}
startRunning(100).then(printTime, handleError);
Working demo: http://jsfiddle.net/jfriend00/fbmbrc8s/
FYI, my design preference would probably be to have a public athlete object and then methods on that object to start running, stop running, etc...
Here are some of the fundamental things you got wrong in a use of promises:
They are one-shot objects. They are resolved or rejected only once.
The structure startRunning().then(checkIsFinished) just doesn't make logical sense. For the first part of this to work, startRunning() has to return a promise, and it has to resolve ore reject that promise when something useful happens. Your are just resolving it after two seconds which doesn't really seem to accomplish anything useful.
The words of your description make it sound like you want `checkIsFinished() to keep going an not resolve its promise until the athlete is finished. It is possible to do that by continually chaining promises, but that seems a very complicated way to do things and certainly not necessary here. Further, that isn't at all what your code attempts to do. Your code just returns a new promise that is never resolved unless the athelete has already passed the desired distance. If not, it returns a promise that is never resolved or rejected. This is a fundamental violation of promise concepts. A function that returns a promise is responsible for eventually resolving or rejecting it unless the calling code expects to just abandon the promise in which case it's probably the wrong design tool.
Here's another approach that creates a public Athlete() object that has some methods and allows multiple people to be watching the progress:
var EventEmitter = require('events');
function Athlete() {
// private instance variables
var runInterval, startTime;
var watcher = new EventEmitter();
// public instance variables
this.timeTaken = 0;
this.distanceTravelled = 0;
this.startRunning = function() {
startTime = Date.now();
var self = this;
if (runInterval) {clearInterval(runInterval);}
runInterval = setInterval(function() {
self.distanceTravelled += 25;
self.timeTaken = Date.now() - startTime;
console.log("distance = ", self.distanceTravelled);
// notify watchers
watcher.emit("distanceUpdate");
},2500);
}
this.notify = function(limit) {
var self = this;
return new Promise(function(resolve, reject) {
function update() {
if (self.distanceTravelled >= limit) {
watcher.removeListener("distanceUpdate", update);
resolve(self);
// if no more watchers, then stop the running timer
if (watcher.listeners("distanceUpdate").length === 0) {
clearInterval(runInterval);
}
}
}
watcher.on("distanceUpdate", update);
});
}
}
var a = new Athlete();
a.startRunning();
a.notify(100).then(function() {
console.log("done");
});