Promise flattening fails - why? - javascript

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("...")
*/

Related

How to set time gap between each iteration of foreach() in js [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
The community reviewed whether to reopen this question 3 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I am running an event loop of the following form:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i. Any recommendations on how to fix this?
The for loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i will be at its last value for all the callbacks.
This is because the for loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.
To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).
As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let to define the for loop variable and it will be uniquely defined for each iteration of the for loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.
Use .forEach() to iterate since it creates its own function closure
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Create Your Own Function Closure Using an IIFE
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Create or Modify External Function and Pass it the Variable
If you can modify the asynchronousProcess() function, then you could just pass the value in there and have the asynchronousProcess() function the cntr back to the callback like this:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Use ES6 let
If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let declared in a for loop declaration like this will create a unique value of i for each invocation of the loop (which is what you want).
Serializing with promises and async/await
If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async and await, then you have more options.
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
This will make sure that only one call to asynchronousProcess() is in flight at a time and the for loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await, the containing function must be declared async.
Run asynchronous operations in parallel and use Promise.all() to collect results in order
function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}
someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
async await is here
(ES7), so you can do this kind of things very easily now.
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess is returning a Promise
If asycronouseProcess is not in your control then you can make it return a Promise by yourself like this
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess(); by await asyncProcess();
Understanding Promises before even looking into async await is must
(Also read about support for async await)
Any recommendation on how to fix this?
Several. You can use bind:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
Or, if your browser supports let (it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
Or, you could do the job of bind manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let when I can use it (e.g. for Firefox add-on); otherwise bind or a custom currying function (that doesn't need a context object).
var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.
ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();
JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
The solution depends on what you really need. If the example is close to exactly what you need, #Simon's suggestion to pass i to your async process is a good one.

Javascript array only running function that returns a promise on one element of array

Ive posted other questions but feel I should simplify things
I have one function that sets a context and calls a second function to draw the lines on the context.
I have this:
var arr = [];
which is populated like this:
arr = [context,pre];
while pre looks like pre = [[{x:n,y:m}],[{x:j,y:k}]];
So, basically, I have an array, pre, containing arrays of coordinates. That array is pushed with a context into arr
arr is returned and pushed into a final array, lets say final_arr, which now should look like this: final_arr = [[context1,pre1],[context2,pre2],...]
My goal is to loop through final_arr and draw lines on different contexts, determined by the context in the array. For example, the first iteration will access final_arr[0] and contain context1,pre1. These two values are sent to a function, wrap(context, pre) that returns a promise. Inside this wrap function, another function is called, animate(pre[i]). this function takes each element in pre, which corresponds to an array of coordinates, and actually draws the line using animation frames. animate() also returns a promise.
Currently, only one of the paths is being drawn, which seems to be because only one value of final_arr is being used, even though I am iterating through it
My attempts to iterate:
final_arr.reduce((a,c) => a.then(() => wrap(c[0],c[1])), Promise.resolve());
and
var temp = Promise.resolve();
var i = 0;
for (i = 0; i < arr.length; i++){
//window.alert(arr[i].length)
var ct = arr[i][0];
var line = arr[i][1];
temp.then(() => wrap(ct,line));
}
and here are the functions being called:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
function wrap(ctx, lines){
return new Promise(function(resolve) {
var counter = 0;
t = 1;
var getAnimation = function(){
if(counter < lines.length){
context = ctx;
lines.reduce((a, c) => a.then(() => animate(c)), Promise.resolve());
counter++;
} else {
resolve()
}
};
getAnimation();
});
}
The context variable set in wrap is a global variable for the js file
I hope the question asked this way provides clarity as to what I am having a problem with
Thank you for any help
Edit:
Attempted fiddle
Edit2:
Oddly enough this works
if(final_arr.length == 1){
wrap(final_arr[0][0], final_arr[0][1]);
} else if (final_arr.length == 2){
wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1]));
} else if (final_arr.length == 3){
wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1])).then(wrap(final_arr[2][0], final_arr[2][1]));
}
But when using this, the lines are drawn at the same time (which is okay, but not preferred)
edit: just spotted the missing resolve inside the if statement of wrap => the returned Promise will never be resolved...
I recommend to start with a much simpler version of wrap before making any micro-optimizations:
function wrap(ctx, lines){
return new Promise(function(resolve) {
lines.forEach(p => animate(p, ctx));
resolve();
});
}
callbacks to requestAnimationFrame are called after finishing all microtasks (i.e. after all Promises) - see When will requestAnimationFrame be executed?
so the value of the global variable context will be the same for all of the callbacks, i.e. the same line drawn multiple times or a race condition or something depending on internals of the context
I would get rid of globals, using only function params and locals:
var animate = function(p, ctx) {
var t ...
... ctx.beginPath()

javascript loop promises iterating with array in arguments

I am trying to loop through an array of AsynWork to be done. And cant flood the system with async work done all at the time. so I am trying to do one by one with promises. My problem is that I need to go though an array of values so each async works on each value of the array. I managed to do it with this code, but it works for my specific case. Can't make it general. What would be the approach to make it reusable for other type of arrays? I have seen some solutions with array.reduce then promises but cant figure it out. Also have seen examples with Q but not using, if it can be done with simple javascript would be better.
My Code:
function doSomething(ObjIn1, ObjIn2) {
return new Promise(function(resolve, reject) {
console.log("doSomething: ObjIn1: " + ObjIn1 + " ObjIn2: " + ObjIn2);
setTimeout(function() {
console.log("doSomething Done: ObjIn1: " + ObjIn1 + " ObjIn2: " + ObjIn2);
resolve(ObjIn1, ObjIn2);
}, 500);
})
}
function LoopPromises(Function2Loop, functionOptions, Counter, Max) {
console.log("Counter: " + Counter);
if (Counter < Max) {
Function2Loop.apply(this, [functionOptions[0][Counter], functionOptions[1]]).then(function() {
Counter++;
LoopPromises(Function2Loop, functionOptions, Counter, Max);
});
}
}
LoopPromises(doSomething, [
["A1", "A2", "A3"], "ARG2TESTE"
], 0, 3)
You're overthinking this :) A function with arguments is the same as a function without arguments closing over a function with arguments so:
a(1,2,3,4);
Is the same as
(() => a(1,2,3,4))();
Except perhaps negligibly slower. I'm assuming you need to queue the work for an arbitrary amount of promises. If you need to do it for a fixed number - you can just then between them. Let's see how we can do this:
// runs fn on the array elements in sequence, but
function sequence(fns) { // fns - functions returning promises
return fns.reduce((prev, nextFn) => { // 'fold' the array
return prev.then(nextFn); // after the previous is done, execute the next
}, Promise.resolve()); // start with an empty promise
}
Make sure you understand reduce first. For convenience - let's see an example without it:
function sequence(fns) { // fns - functions returning promises
var queue = Promise.resolve();
fns.forEach(fn => queue = queue.then(fn));
return queue;
}
We're iterating through our array of work (functions) and executing them one after the other where we execute the next after the promise the previous returned resolved.
Where the values wait for each other based on the promise resolving (via then). This would let you do:
sequence([
() => new Promise(r => setTimeout(r, 500));
() => console.log("I only run after the previous work completed");
]);

Nested promises to achieve sequential execution

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/

How to sync JavaScript callbacks?

I've been developing in JavaScript for quite some time but net yet a cowboy developer, as one of the many things that always haunts me is synching JavaScript's callbacks.
I will describe a generic scenario when this concern will be raised: I have a bunch of operations to perform multiple times by a for loop, and each of the operations has a callback. After the for loop, I need to perform another operation but this operation can only execute successfully if all the callbacks from the for loop are done.
Code Example:
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
Suggested Solution:
Initiate a counter at the beginning of the loop holding the length of the loop, and each callback decrements that counter. When the counter hits 0, execute myFunc2. This is essentially to let the callbacks know if it's the last callback in sequence and if it is, call myFunc2 when it's done.
Problems:
A counter is needed for every such sequence in your code, and having meaningless counters everywhere is not a good practice.
If you recall how thread conflicts in classical synchronization problem, when multiple threads are all calling var-- on the same var, undesirable outcomes would occur. Does the same happen in JavaScript?
Ultimate Question:
Is there a better solution?
The good news is that JavaScript is single threaded; this means that solutions will generally work well with "shared" variables, i.e. no mutex locks are required.
If you want to serialize asynch tasks, followed by a completion callback you could use this helper function:
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
The first argument is the array of values that needs to be passed in each pass, the second argument is a loop callback (explained below) and the last argument is the completion callback function.
This is the loop callback function:
function loopFn(nextTask, value) {
myFunc1(value, nextTask);
}
The first argument that's passed is a function that will execute the next task, it's meant to be passed to your asynch function. The second argument is the current entry of your array of values.
Let's assume the asynch task looks like this:
function myFunc1(value, callback)
{
console.log(value);
callback();
}
It prints the value and afterwards it invokes the callback; simple.
Then, to set the whole thing in motion:
serializeTasks([1,2, 3], loopFn, function() {
console.log('done');
});
Demo
To parallelize them, you need a different function:
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
And your loop function will be this (only parameter name changes):
function loopFn(doneTask, value) {
myFunc1(value, doneTask);
}
Demo
The second problem is not really a problem as long as every one of those is in a separate function and the variable is declared correctly (with var); local variables in functions do not interfere with each other.
The first problem is a bit more of a problem. Other people have gotten annoyed, too, and ended up making libraries to wrap that sort of pattern for you. I like async. With it, your code might look like this:
async.each(someArray, myFunc1, myFunc2);
It offers a lot of other asynchronous building blocks, too. I'd recommend taking a look at it if you're doing lots of asynchronous stuff.
You can achieve this by using a jQuery deferred object.
var deferred = $.Deferred();
var success = function () {
// resolve the deferred with your object as the data
deferred.resolve({
result:...;
});
};
With this helper function:
function afterAll(callback,what) {
what.counter = (what.counter || 0) + 1;
return function() {
callback();
if(--what.counter == 0)
what();
};
}
your loop will look like this:
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
here afterAll creates proxy function for the callback, it also decrements the counter. And calls whenAllDone function when all callbacks are complete.
single thread is not always guaranteed. do not take it wrong.
Case 1:
For example, if we have 2 functions as follows.
var count=0;
function1(){
alert("this thread will be suspended, count:"+count);
}
function2(){
//anything
count++;
dump(count+"\n");
}
then before function1 returns, function2 will also be called, if 1 thread is guaranteed, then function2 will not be called before function1 returns. You can try this. and you will find out count is going up while you are being alerted.
Case 2: with Firefox, chrome code, before 1 function returns (no alert inside), another function can also be called.
So a mutex lock is indeed needed.
There are many, many ways to achieve this, I hope these suggestions help!
First, I would transform the callback into a promise! Here is one way to do that:
function aPromise(arg) {
return new Promise((resolve, reject) => {
aCallback(arg, (err, result) => {
if(err) reject(err);
else resolve(result);
});
})
}
Next, use reduce to process the elements of an array one by one!
const arrayOfArg = ["one", "two", "three"];
const promise = arrayOfArg.reduce(
(promise, arg) => promise.then(() => aPromise(arg)), // after the previous promise, return the result of the aPromise function as the next promise
Promise.resolve(null) // initial resolved promise
);
promise.then(() => {
// carry on
});
If you want to process all elements of an array at the same time, use map an Promise.all!
const arrayOfArg = ["one", "two", "three"];
const promise = Promise.all(arrayOfArg.map(
arg => aPromise(arg)
));
promise.then(() => {
// carry on
});
If you are able to use async / await then you could just simply do this:
const arrayOfArg = ["one", "two", "three"];
for(let arg of arrayOfArg) {
await aPromise(arg); // wow
}
// carry on
You might even use my very cool synchronize-async library like this:
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
And last but not least, use the fine async library if you really don't want to use promises.
const arrayOfArg = ["one", "two", "three"];
async.each(arrayOfArg, aCallback, err => {
if(err) throw err; // handle the error!
// carry on
});

Categories