I have a function that calls itself a couple of times when a promise is resolved, and when it is done calling itself, it resolves the promise like so:
var counter = 0;
function runStep(past_resolved_content) {
return new Promise(function(resolve, reject){
//do something different with counter
if (counter < reference ) {
counter++;
var step_ran = somePromiseReturningFunction();
step_ran.then(runStep);
} else {
resolve("message");
}
From what I can tell, the function does what I want, it will call the somePromiseReturningFunction() correctly and in the desired order.
I have a second function that must call runStep() and continue execution when the promise returned by it is resolved.
function executeAllSteps() {
runStep().then(
function(){
console.log("resolved outer promise");
});
}
My problem is that I cannot make the executeAllSteps function work as desired. I have tried several syntax for the .then but nothing produced the desired output.
Either the content is called immediately, without the promise resolving ( when I write it like this: runStep().then(console.log("resolved outer promise"));, or never at all.
Does anyone know how to correct this function?
Note: I need to use the native javascript promise implementation.
Once you go inside the if (counter < reference ) { condition, you will never resolve the promise that you created in that invocation of runStep() and thus the .then() handler in executeAllSteps() never fires. You need to find a place in runStep() where you resolve the promise you created for each invocation of runStep(), whether or not you've reach your counter max.
I'm not sure I fully understand what you're trying to achieve, but you could do this:
var counter = 0;
function runStep(past_resolved_content) {
return new Promise(function(resolve, reject) {
//do something different with counter
if (counter < reference ) {
counter++;
var step_ran = somePromiseReturningFunction();
step_ran.then(runStep).then(resolve); // <== change made here
} else {
resolve("message");
}
});
}
Logically, this will call call the first runStep() which will execute somePromiseReturningFunction() and when that is done (when it's promise is resolved), it will call a nested runStep() and when that inner runStep resolves, the outer one will resolve itself. So what will happen is you will nest several calls to runStep() until your counter gets to the reference value and then that inner runStep will resolve itself. That will allow the next-most outer runStep to resolve itself, then the next most outer runStep to resolve itself continuing until it gets to the most outer runStep which will resolve itself and then all then .then() handler in executeAllSteps() which it sounds like is what you want.
I suspect that if I understood more what you're really trying to accomplish rather than this somewhat abstract code example, then I could probably find a much cleaner way to do this, but I don't really follow what you want to do in what conditions.
There are several different approaches you could take that will use existing promises rather than always creating new ones. Here's one idea:
var counter = 0;
function runStep(past_resolved_content) {
//do something different with counter
if (counter < reference ) {
counter++;
return somePromiseReturningFunction().then(runStep);
} else {
// return a resolved promise with your message
return Promise.resolve("message");
});
}
}
You could also avoid calling runStep recursively when the counter has reached its value which simplifies things a bit (assumes you don't call runStep the first time when counter has already exceeded the value):
var counter = 0;
function runStep(past_resolved_content) {
counter++;
return somePromiseReturningFunction().then(function(data) {
if (counter < reference) {
return runStep(data);
} else {
return data;
}
});
}
Working demo of this idea: http://jsfiddle.net/jfriend00/PfEV2/
And, if you don't need to manipulate the counter variable as the process unfolds, then you could do it in a loop too where runStep doesn't have to call itself and the counter doesn't have to be a public variable:
function runStep() {
var p = Promise.resolve();
for (var counter = 0; counter < reference; counter++) {
p = p.then(somePromiseReturningFunction);
}
return p;
}
Working demo: http://jsfiddle.net/jfriend00/33ZZ6/
Related
This question already has answers here:
When is the body of a Promise executed?
(4 answers)
Closed 2 years ago.
Anyone has any insight on why the following code works?
Notice it's still using var in loop rather than let.
for( var i = 0; i < 5; i++) {
new Promise((resolve, reject) => {
console.log(i);
resolve();
});
}
// Output: 0,1,2,3,4
We know that if setTimout is used here, it queues up 5 macrotasks and will reference i in closure after loop is done, printing out all 5's eventually.
But if promise is being used here, why the microtask queue can accurately scope the var i value?
Any reference would be appreciated.
Though I was unable to find any direct citation explaining this it has been my experience that the executor function to a Promise constructor is executed synchronously.
Though it provides the resolve and reject functions as callbacks to manage any asynchronous code being evaluated or queued within the executor function the executor function itself is not only synchronous but also executed synchronously in the greater context of the Promise object being instantiated.
That would explain why the console.log will log in order within that loop but using a setTimeout would be performed deferred after the variable i has since moved on to its final value.
That happens because that code never gets executed in any microtask. The promise executor is called inside the Promise constructor function, only the then callbacks are called inside a microtask.
You can see this if you run this code:
new Promise((res, rej) => {
console.log("Hi from the promise executor!");
res();
}).then(() => {
console.log("Hi from the `then` callback (from a microtask)");
});
console.log("Stack emptied");
So your code is equivalent to this:
for (var i = 0; i < 5; i++) {
((resolve, reject) => {
console.log(i);
// resolve();
})();
}
To start with, Promise callback argument is invoked synchronously, so there's no surprise in there, but regarding this statement:
We know that if setTimout is used here, it queues up 5 macrotasks and will reference i in closure after loop is done, printing out all 5's eventually.
This is wrong.
for(var i = 0; i < 5; i++)
setTimeout(console.log, 0, i);
You can schedule with both setTimeout and setInterval not just callbacks, but the value these callbacks will receive as parameters.
That means that the for loop in there will log 0, 1, 2, 3, and 4, in this exact order.
For a fair comparison, you would want to place the console.log within the resolve like this
for( var i = 0; i < 5; i++) {
new Promise((resolve, reject) => {
resolve(console.log(i));
});
}
Still, this would yield the same results because there is no async delay. But then why does a setTimeout that has 0 delay not produce the same result?
NodeJS has a first in last out callstack, with each cycle referred to as a tick. When a setTimeout resolves, it gets placed in the message queue, which doesn't run until the next tick. When a promise resolves, it gets to cut back in line immediately within the current tick.
The call stacks therefor look like
setTimeout:
timeout i ++ > timeout > i ++ > timeout > i ++ > timeout > i ++ > timeout > i ++ > log > log > log > log > log
promise:
promise > resolve > log > i++ > promise > resolve > log > i++ > promise > resolve > log > i++ > promise > resolve > log > i++ > promise > resolve > log > i++
So you can see that by the time the console.logs within setTimeout are called, i has already been equated to 5, where as the promise console.logs get to cut back in line before i is iterated.
for( var i = 0; i < 5; i++) {
setTimeout( ()=> {
console.log("setTimeout:",i);
});
}
for( var i = 0; i < 5; i++) {
new Promise((resolve, reject) => {
resolve(console.log("Promise:",i));
});
}
Also notice that the Promise console.logs occur before setTimeout console.logs even though the setTimeout loop occurs first. This shows the current tick includes both for loops, so any setTimeout console.log has to wait until they both complete.
I could access the variable 'savedCards ' from the first promise, it has some value. Inside the second promise it's undefined but the variable 'eCard' has value. Please explain me why ?
saveCard(eCard: IEcards) {
var savedCards: IEcards[] = [];
this.storage.get("key").then((value) => {
if (value.saves == undefined) {
var saves = savedCards;
value.saves = saves;
}
savedCards = value.saveCard; // have value and can be accessed
console.log(savedCards);
}).then((data) => {
console.log(savedCards); // savedCards is undefined but eCard.id has value
this.globalProvider.IsCardExist(eCard.id, savedCards).then((data) => {
if (!data.response) {
this.globalProvider.AddEcardToStorage("saves", eCard);
}
});
});
}
When you need to access the intermediate values in your chain, you should split your chain apart in those single pieces that you need. Instead of attaching one callback and somehow trying to use its parameter multiple times, attach multiple callbacks to the same promise - wherever you need the result value.
function getExample() {
var a = promiseA(…);
var b = a.then(function(resultA) {
// some processing
return promiseB(…);
});
return Promise.all([a, b]).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
[Edit]
You want to know why, and here is the answer: ES6 came with generator functions, which allow to break the execution apart in pieces at arbitrarily placed yield keywords. Those slices can be run after each other, independently, even asynchronously - and that's just what we do when we want to wait for a promise resolution before running the next step.
Your code is resolving the second promise before the first one. You can not assure that your code will work as you want by using "then()". If you want a synchronous resolution, you should go for another way.
[Edit 2]
Try to use await and see if you are able to solve your problem. More info here: http://2ality.com/2017/08/promise-callback-data-flow.html
const simplePromise = i => {
return new Promise(function(resolve, reject) {
console.log(i);
setTimeout(function(){
resolve();
}, 2000);
});
}
var anchor = simplePromise(0);
for (var i=1; i<4; i++) {
anchor = anchor.then(_ => simplePromise(i));
}
prints:
0
4
4
4
4
instead of:
0
1
2
3
4
1. Can someone explain why? and 2. tell me how to achieve this?
I can see that the first promise is executed (i=0), then the loop runs and then the value of i(=4) gets passed to the next promise. Shouldn't this be solved by having a function inside then (_ => simplePromise(i)) ?
It's happened due you use var. Try to change var to let and that fix your problem.
UPDATE
That problem more clearly explained at this article and this (scroll to section Difference Details -> Closure in Loop) and great explanation of let key word
EXPLANATION
Let take that piece of code:
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // output '5' 5 times
}, 100);
}
In that example each iteration create function with closure on variable i, which will be executed in the future. Problem is var declare variable which
...is scoped to the nearest function block and let is scoped to the nearest enclosing block, which can be smaller than a function block.
i.e. all of the created functions will create closure to the same variable. And when the execution time comes i === 5. And All of the function will print the same value.
How let solve that problem...
let in the loop can re-binds it to each iteration of the loop, making sure to re-assign it the value from the end of the previous loop iteration, so it can be used to avoid issue with closures.
Your mistake is one of most common mistakes in JS - if not the most common - using a for loop to manipulate a state variable in an asynchronous situation. Your promises and your loop do not run in sync. The loop finishes much faster than any of your promises do.
Yet you use i in your promise callback, which only ever runs after the loop is done. Don't do that. There are ways to prevent it, but this has been discussed so often that I will only suggest to research and read a few of the existing answers.
I strongly suspect that you do not even want to loop over a fixed range of numbers. You actually have an array of items.
Simply drop the for loop and use the array iteration tools to dodge the scoping problem. Array#reduce is the perfect candidate here.
const simplePromise = val => {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log(val);
resolve(val);
}, 200);
});
}
var items = [0,1,2,3,4];
console.log("array iteration starts");
items
.reduce((acc, i) => acc.then(_ => simplePromise(i)), Promise.resolve())
.then(val => console.log("chain execution end w/ " + val));
console.log("array iteration done");
/*
acc = Promise.resolve()
then simplePromise(0)
then simplePromise(1)
then simplePromise(2)
then simplePromise(3)
then simplePromise(4)
then console.log("...")
*/
A user is able to make asynchronous calls by entering a value in a UI.
When the user changes the value in the UI, another async call is made - possibly before the callback supplied to the promise returned from the async call has been invoked.
The async API being used returns a Q based promise.
How can I cancel the original call gracefully, ensuring that even if the system returns with a value for the first call, the .then part of the promise is not invoked with that value (but that it is eventually invoked when the second async call completes)?
Back in the day I had a case like this (node.js) where I was either doing a web or a db request. I had a timeout object, which I think was cancellable (sry, don't remember the details) and had promises on both of them. So if the webpage returned first, I'd cancel the timeout and return the html. And if the timeout happened first, I updated a "timedout" field in some JSON to yes, so that if the web call ever returned, it would know just to die. It was a little mind-blowing, because with these promises, I could enter the function once, and actually return twice!
Hope that helps
-Tom
Absent some cancellation API, each async call will run to completion, but if all you're looking for is canceling the .then() functions, then that's doable. The most straight-forward way is a counter:
var counter = 0;
function onClick() {
counter++;
doAsyncCall().then(function(result) {
if (!--counter) {
myFunc(result);
}
});
}
But you asked for graceful, so maybe a more reusable construct would be something like this:
var idle;
function onClick(){
idle = Overtake(idle, doAsyncCall());
idle.then(myFunc).then(myOtherFunc);
}
which could be implemented like this:
function Overtaker(prior, callp) {
if (prior) {
prior.cancel();
}
var p;
p = new Promise(function(resolve, reject) {
callp.then(function(value) { return p && resolve(value); },
function(reason) { return p && reject(reason); });
});
this.cancel = function() { p = null; }
this.then = function(onFulfilled, onRejected) {
return p.then(onFulfilled, onRejected);
}
}
function Overtake(prior, p) {
return new Overtaker(prior, p);
}
I'm using ES6 promises, but Q promises seem similar, so hopefully this works there as well.
I'm trying to work through this js/async scenario and i'm trying to know how the rest of the js world handles this.
function doStuff(callback) {
cursor.each(function(err, blahblah) {
...doing stuff here takes some time
});
... Execute this code ONLY after the `cursor.each` loop is finished
callback();
EDIT
Here's a more concrete example updated using most of the suggestions below which still doesn't work.
function doStuff(callback) {
MongoClient.connect(constants.mongoUrl, function(err, db) {
var collection = db.collection('cases2');
var cursor = collection.find();
var promises = []; // array for storing promises
cursor.each(function(err, item) {
console.log('inside each'); // NEVER GETS LOGGED UNLESS I COMMENT OUT THIS LINE: return Q.all(promises).then(callback(null, items));
var def = Q.defer(); // Create deferred object and store
promises.push(def.promise); // Its promise in the array
if(item == null) {
return def.resolve();
}
def.resolve(); // resolve the promise
});
console.log('items'); // ALWAYS GETS CALLED
console.log(items);
// IF I COMMENT THIS LINE OUT COMPLETELY,
// THE LOG STATEMENT INSIDE CURSOR.EACH ACTUALLY GETS LOGGED
return Q.all(promises).then(callback(null, items));
});
}
without using promises or any other dependencies/libraries you can simply
function doStuff(callback) {
add a counter
var cursor = new Array(); // init with some array data
var cursorTasks = cursor.length;
function cursorTaskComplete()
{
cursorTasks--;
if ( cursorTasks <= 0 ) {
// this gets get called after each task reported to be complete
callback();
}
}
for ( var i = 0; i < cursor.length; i++ ) {
...doing stuff here takes some time and does some async stuff
check after each async request
...when async operation is complete call
cursorTaskComplete()
}
}
Without knowing the details of the async calls you're making within the cursor.each loop, I shall assume that you have the ability to invoke a callback each time the functions invoked therein have completed their async task:
function doStuff() {
var promises = []; // array for storing promises
cursor.each(function(err, blahblah) {
var def = Q.defer(); // create deferred object and store
promises.push(def.promise); // its promise in the array
call_async_function(..., def.resolve); // resolve the promise in the async function's callback
});
// pass the array to Q.all, only when all are resolved will "callback" be called
return Q.all(promises);
}
and the usage then becomes:
doStuff().then(callback)
Note how the invocation of the callback now never touches the doStuff function - that function now also returns a promise. You can now register multiple callbacks, failure callbacks, etc, all without modifying doStuff. This is called "separation of concerns".
[NB: all the above based on the Q promises library - https://github.com/kriskowal/q]
EDIT further discussion and experimentation has determined that the .each call is itself async, and gives no indication to the outside when the last row has been seen. I've created a Gist that demonstrates a resolution to this problem.
if you want to do it with the async module, you can make use of the async forEachSeries function
Code snippet:
function doStuff(callback) {
async.forEachSeries(cursor, function(cursorSingleObj,callbackFromForEach){
//...do stuff which takes time
//this callback is to tell when everything gets over execute the next function
callbackFromForEach();
},function(){
//over here the execution of forEach gets over and then the main callback is called
callback();
});
}
In my mind an elegant/ideal solution would be to have something like
cursor.each(........).then( function() { ....your stuff});
But without that you can do this....UPDATED
http://plnkr.co/edit/27l7t5VLszBIW9eFW4Ip?p=preview
The gist of this is as shown below...notice....when
var doStuff = function(callback) {
cursor.forEach(function(cursorStep) {
var deferred = $q.defer();
var promise = deferred.promise;
allMyAsyncPromises.push(promise);
cursorStep.execFn(cursorStep.stepMeta);
promise.resolve;
});
$q.when(allMyAsyncPromises).then(callback);
}
After hitting the start button wait for few seconds...the async tasks have been simulated to finish in 5 seconds so the status will update accordingly.
Not having access to a real cursor object..I had to resort of fake cursor like and array.