Trying to define a promise.all - javascript

I am trying to understand how promises work by creating the Promise.all method from an exercise of this book:
https://eloquentjavascript.net/11_async.html#i_Ug+Dv9Mmsw
I tried looping through the whole array that's given as argument to the method itself, using .then for successful promises where the body of the handler pushes the result to a binding i defined previously outside of the loop, for rejected promises, i used .catch in a way that it takes the rejected value as a "reason" and rejects the main promise giving it an error
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [];
for(let i=0; i<promises.length ; i++){
promises[i]
.then(x => fullArray.push(x))
.catch(reason => reject(new Error(reason)));
}
resolve(fullArray);
});
}
What i expected the function to do the following:
-Pick a promise from the "Promises" array.
-Solve the promise (if successful) by using the .then method on it with a handler function that just pushes the result to "fullArray".
-Solve the promise (if rejected) by using the .catch method on it with a handler function that simply calls the reject handler of the main promise that will be returned by "Promise_all".
-When the loop finishes, simply resolve the promise the "fullArray" of successful promises.
The code simply doesn't work the way i thought it would, using the test code of the book doesn't return the expected results:
Promise_all([]).then(array => {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(resolve => {
setTimeout(() => resolve(val), Math.random() * 500);
});
}
Promise_all([soon(1), soon(2), soon(3)]).then(array => {
console.log("This should be [1, 2, 3]:", array);
});
Promise_all([soon(1), Promise.reject("X"), soon(3)])
.then(array => {
console.log("We should not get here");
})
.catch(error => {
if (error != "X") {
console.log("Unexpected failure:", error);
}
});

The issue with the code, as #Bergi said, was that the ".then" and ".catch" callbacks are called asynchronously and as a consequence of this, "fullArray" wasn't even filled when "resolve()" was called even when it was outside of the loop. To solve this, i simply added "resolve()" inside of the promise that finishes last, to do this, i simply added a "counter" binding that has the value of the length of the "promises" array, and is reduced by 1 every time ".then" was called on a promise and when this "counter" equals 0, it calls "resolve()".
But that only solves the problem of filling the "fullArray" with the promises, not the issue of those promises being ordered correctly by the order they were called, to solve this i simply enumerated them in the array with the "i" binding of the loop, the end result was this:
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [],
counter = promises.length;
for(let i=0; i< promises.length ; i++){
promises[i]
.then(x => {
fullArray[i] = x;
counter--;
if(counter == 0) resolve(fullArray)})
.catch(reason => reject(new Error(reason)));
}
});
}

Related

JavaScript Promise.all with predicate

First given an array with a list of promises:
var promises = [promise1, promise2, promise3];
I would like to be able to execute all these promises with a predicate. If the first predicate returns false then return immediately giving a result of what has been processed up to that point and cancelling any remaining promises. For example something like:
Promise.all(promises, p => p != false))
.then(results => console.log(results));
Results should have what has been processed up to the point where the first predicate failed.
The promises should be processed in series not parallel.
You cannot cancel a promise. You could however just start the next action when the previous was done:
async function doUntil(generator, predicate) {
const result = [];
let curr;
while(predicate(curr = await generator()))
result.push(curr);
return result;
}
// usable as:
const users = await doUntil(
() => User.getOneAsync(),
user => user.loggedIn
);
For sure that can easily be adopted to a list of promises, however then those promises that are not part of the result still get executed, there results go into nowhere:
const promises = [/*...*/];
const result = await doUntil(
() => promises.shift(),
p => p
);
My solution is very similar to the one promise by Yona, but in the sense that you only return the last returned promise value that satisfies the predicate.
In the code below, I simply constructed a test promise that returns a random number after 500ms. The random number is then passed into a predicate which returns true of the number exceeds 0.5.
An improvement is that we try to catch an event where the promise might fail. If the promise fails then the chain is immediately broken returning the last result, similar to if the promise returns a result that fails the predicate:
// Here we simply set a test promise that returns a random number after 0.5s
function testPromise() {
return new Promise(resolve => {
window.setTimeout(() => resolve(Math.random()), 500);
});
}
// Execute promises in a specified order, evaluate their output by a predicate
async function awaitSequentialPromises(promises, predicate) {
let result;
for (const promise of promises) {
// Try, so that we can catch if a promise fails
try {
const _result = await promise();
console.log(_result);
// Break out of loop if promise returns results that fails predicate
if (!predicate(_result))
break;
else
result = _result;
} catch(e) {
// Break out of loop if promise fails
break;
}
}
console.log(`Final result: ${result}`);
return result;
}
// Our predicate is simply if the promise returns a random number > 0.5
awaitSequentialPromises([testPromise, testPromise, testPromise], v => v > 0.5);
Here's a function that should do what you want. You may want to modify it to handle failing predicates differently.
As others have noted, you cannot "cancel" a promise, since it represents something that has already started, generally speaking.
/**
* Combines an array of promises with a predicate. Works like Promise.all, but checks each result from the given
* Promise against the supplied predicate, and if the result value from any Promise fails the predicate, the
* overall promise is immediatly resolved with those values which passed the predicate.
*/
function allWithPredicate(
promises /*: Array<Promise<T1>, Promise<T2>, ...> */,
predicate /*: (mostRecentResult, allResults, promiseIndex)*/
) /*: Promise<[T1, T2, ...]> */ {
// Handle empty input arrays
if (promises.length === 0)
return Promise.resolve([]);
// Create a manual result promise, as none of the built-in promise manipulation functions will do the trick.
return new Promise(
(resolve, reject) => {
// This array will collect the values from the promises. It needs to be the same size as the array of
// promises.
const results = new Array(promises.length);
// We track the number of resolved promises so we know when we're done. We can't use the length of the
// array above because the last promise might finish first, which would cause the array to be equal in
// length to the promises array.
let resolvedCount = 0;
// Now we go through each promise and set things up
promises.forEach(
(promise, index) => {
promise.then(
result => {
// A promise has finished successfully! Hooray. We increment the counter.
resolvedCount++;
// Now we check if the newly returned value from the promise is acceptable, using the
// supplied predicate.
if (predicate(result, results, index)) {
// If if it, we keep this result.
results[index] = result;
// If the resolved counter is equal to the original number of promises in the array,
// we are done and can return. Note that we do NOT check against the length of
// the promises array, as it may have been mutated since we were originally called.
if (resolvedCount === results.length) {
resolve(results);
}
} else {
// If not, we short-circuit by resolving the overall promise immediately.
// Note that as written, we do NOT include the unacceptable value in the result.
resolve(results);
}
},
error => {
reject(error);
}
)
}
)
}
);
}
Here's a link to a jsbin demo: https://jsbin.com/jasiguredu/edit?js,console
Promise.all only takes one argument
var promises = [promise1, promise2, promise3];
Promise.all(promises)
.then(p => {
if(p === false) {
throw new Error(false);
} else {
console.log(p);
}
})
.catch(error => {
console.log(error);
});
}
If a promise in Promise.all ends in a catch statement, then it finishes, you can have a counter outside the promise if you need.

Return a promise when foreach loop ends

I'm wanting to run a forEach loop then return (resolve?) a Promise so that my next function can run. I can correctly work out when the forEach is at the end but just dont know how to resolve the Promise.
Here is my code:
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if(i == submittedFields.length - 1) {
console.log('finished');
}
});
}
The reason I want to return a Promise is because I want to wait until this function has run then run another function like this:
this.submissionsProvider.addSubmissionsForms(submissionKey, this.submittedFields)
.then(() => {
this.navCtrl.pop();
});
So if you want to use promises you can do the following:
As a side note you can resovle any value e.g. resolve('finished')
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
return new Promise((resolve, reject) => {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if (i == submittedFields.length - 1) {
resolve();
}
});
reject();
});
}
If the function addSubmissionsForms does nothing async (like e.g. an ajax call or reading and writing into database) you don't really need promises because you just can execute functions one after another
addSubmissionsForms();
nextFunction();

Extra promise at start and Extra promise at end

New at promises, so feel free to be verbose.
I am writing a function "extra_promises_at_start_and_end" that returns a promise to do something.
This function may know immediately that it will fail (ie: return a promise that is rejected). Question 1: Is there something like Promise.give_me_a_rejected_promise(..) or do I have to create a promise and reject it just like my code does?
Similarly, my function "extra_promises_at_start_and_end" calls other functions that return promises. At the end of this async chaining of work, I need to some final processing. Question 2a/2b: Since my function returns a promise, I need to create a promise to do this last bit of work. Is this correct that I need to create a promise and immediately accepted or reject it? Is there a Promise.give_me_a_rejected_promise(..).
My code works as expected, just feels like I am missing something, and so generating redundant code.
Code in question:
// this is the function that may have redundant code
// see question 1 and 2
function extra_promises_at_start_and_end() {
// fake out some module scope variable that indicates if this call is allowed to proceed or not
let ok_to_proceed = Math.random() > 0.5
// this function "extra_promises_at_start_and_end returns" a promise,
// Question 1: I need to create a Promise just to reject it immediatly?
if (!ok_to_proceed) {
return new Promise((resolve, reject) => { reject("failed before starting anything") }) // feels wrong
}
// do 5 things in sequence
return another_module_promise_to_do_something(1).then(() => {
return another_module_promise_to_do_something(2)
}).then(() => {
return another_module_promise_to_do_something(3)
}).then(() => {
return another_module_promise_to_do_something(4)
}).then(() => {
return another_module_promise_to_do_something(5)
}).then(() => {
// need to do something after the above 5 tasks are done,
console.log("doing something after all 5 things are done")
// this function "extra_promises_at_start_and_end" returns a promise,
// Question 2a: I need to create a promise just to resolve it immediatly?
return new Promise((resolve, reject) => { resolve(); }) // feels wrong
}).catch((id) => {
// this function extra_promises_at_start_and_end returns a promise,
// Question 2b: I need to create one just to reject it immediatly?
return new Promise((resolve, reject) => { reject(id); }) // feels wrong
})
}
The caller of this code is expecting a promise.
// run the test
console.log("calling something that will return a promise to let me know when it's done");
extra_promises_at_start_and_end()
.then(() => {
console.log("done :)")
}).catch((id) => { console.log("failed id = " + id) })
Finally, a stub for testing my function
// pretend this is a complex task (ie: not suitable for inlining)
// done by some other module
// it returns a promise
function another_module_promise_to_do_something(id) {
console.log("starting " + id)
let P = new Promise((resolve, reject) => {
console.log(" inside promise " + id)
setTimeout(() => {
if (Math.random() > 0.1) {
console.log(" finished " + id);
resolve();
} else {
console.log(" failed " + id)
reject(id);
}
}, Math.random() * 1000)
})
return P;
}
If this is the way it is supposed to be done, then let me know and I will stop searching for the correct way to use promises.
What I learned is:
Promise.resolve(value) method returns a Promise object that is resolved with the given value. So anyone calling my function can do a then on the response.
Promise.reject(reason) method returns a Promise object that is rejected with the given reason. So any chaining will fail as needed.
Any return value in then will get encapsulated in a promise. Feels like this obscures the intent. So not using.
My new function is as follows:
function promise_something() {
// fake out some module scope variable that indicates if this call is allowed to proceed or not
let ok_to_proceed = Math.random() > 0.5
if (!ok_to_proceed) {
return Promise.reject("failed before starting anything")
}
// do 5 things in sequence
return another_module_promise_to_do_something(1).then(() => {
return another_module_promise_to_do_something(2)
}).then(() => {
return another_module_promise_to_do_something(3)
}).then(() => {
return another_module_promise_to_do_something(4)
}).then(() => {
return another_module_promise_to_do_something(5)
}).then(() => {
// need to do something after the above 5 tasks are done,
console.log("doing something after all 5 things are done")
return Promise.resolve()
}
The then() and catch() functions of a Promise return a Promise.
Your last big chunk of code is indeed redundant. If you needed to do any processing after the 5th chained invocation of then(), you could just chain another then().
Here's a more bare-bones version of your code to illustrate:
const countFromOneToFive = () => {
if (Math.random() > 0.5) {
return Promise.reject("Cosmic radiation ruined your promises. Great.");
}
return Promise.resolve([])
.then((countToFive) => {
countToFive.push(1);
return countToFive;
})
.then((countToFive) => {
countToFive.push(2);
return countToFive;
})
.then((countToFive) => {
countToFive.push(3);
return countToFive;
})
.then((countToFive) => {
countToFive.push(4);
return countToFive;
})
.then((countToFive) => {
countToFive.push(5);
return countToFive;
});
};
countFromOneToFive()
.then((countToFive) => {
countToFive.forEach((number) => console.log(number));
})
.catch((error) => {
console.log(error, "Curses!");
});
You can use Promise.reject() to just return a rejected Promise. This is handled in the catch statement in the bottom.
You can do all of your processing with as many then() invocations as you want. You can simply return after your final then() to have a Promise. From there, treat it like any promise and append then()s and catch()s as you see fit.
1: Is there something like Promise.give_me_a_rejected_promise(..) or do I have to create a promise and reject it just like my code does?
like Promise.reject(err)?
// Question 2a: I need to create a promise just to resolve it immediatly?
The point where this is written in your code is inside a promise chain. You don't need to do anything at all (in that context), to return a resolved Promise, except maybe not throwing.
This Promise will resolve to any value you return, except undefined. And if you return undefined (explicitely or implicitely), this Promise will resolve to the last value before that, in the Promise chain.
Only if you need to explicitely resolve to undefined you'd have to return Promise.resolve().
// Question 2b: I need to create one just to reject it immediatly?
Still inside the Promise chain: Since a rejected value is like a thrown Error in sync code, all you need to do here is to throw.
But that is pointless in the context you asked. Why catching an Error, just to immediately throw the very same Error, without doing anything else in that catch-block.
So your code could be like
function extra_promises_at_start_and_end() {
// fake out some module scope variable that indicates if this call is allowed to proceed or not
let ok_to_proceed = Math.random() > 0.5
if (!ok_to_proceed) {
return Promise.reject("failed before starting anything");
}
// do 5 things in sequence
return another_module_promise_to_do_something(1)
.then(() => another_module_promise_to_do_something(2))
.then(() => another_module_promise_to_do_something(3))
.then(() => another_module_promise_to_do_something(4))
.then(() => another_module_promise_to_do_something(5))
.then(() => {
// need to do something after the above 5 tasks are done,
console.log("doing something after all 5 things are done")
return null; //so the previous value is no longer propagated by this Promise
})
//.catch(id => { throw id }) //is pointless.
}
When you say "This Promise will resolve to any value you return". So returning new Promise((resolve, reject) => { resolve 100}).then .... blaw blaw blaw is the same as 100.then .. blaw blaw blaw ?
Not exactly. Inside a Promise chain, returning a plain value like 100 or a Promise that resolves to 100 (like return Promise.resolve(100)) is equivalent in result ...
var foo1 = somePromise.then(() => {
//do something
return 100
})
//is equivalent in result to
var foo2 = somePromise.then(() => {
//do something
return Promise.resolve(100);
})
//or to
var foo3 = somePromise.then(() => {
//do something
return new Promise(resolve => resolve(100));
})
foo1, foo2, foo3 are all promises that resolve to the value 100 after somePromise has finished. That's equivalent in result.
... but you can't call then() on a number.
//you can do
somePromise.then(() => {
//do something
return 100
}).then(...)
//or sometimes you want to do
somePromise.then((value) => {
//usually because you need `value` inside `.then(...)`
return somethingAsync().then(...)
})
//but you can NOT do
somePromise.then(() => {
//do something
return 100.then(...)
})

How to create a loop of promises

so i have a promise that collects data from a server but only collects 50 responses at a time. i have 250 responses to collect.
i could just concate promises together like below
new Promise((resolve, reject) => {
resolve(getResults.get())
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
}).then((results) => {
totalResults.concat(results)
return getResults.get()
})
In this instance i only need 250 results so this seems a managable solution but is there a way of concating promises in a loop. so i run a loop 5 times and each time run the next promise.
Sorry i am new to promises and if this were callbacks this is what i would do.
If you want to loop and serialise the promises, not executing any other get calls once one fails, then try this loop:
async function getAllResults() { // returns a promise for 250 results
let totalResults = [];
try {
for (let i = 0; i < 5; i++) {
totalResults.push(...await getResults.get());
}
} catch(e) {};
return totalResults;
}
This uses the EcmaScript2017 async and await syntax. When not available, chain the promises with then:
function getAllResults() {
let totalResults = [];
let prom = Promise.resolve([]);
for (let i = 0; i < 5; i++) {
prom = prom.then(results => {
totalResults = totalResults.concat(results);
return getResults.get();
});
}
return prom.then(results => totalResults.concat(results));
}
Note that you should avoid the promise construction anti-pattern. It is not necessary to use new Promise here.
Also consider adding a .catch() call on the promise returned by the above function, to deal with error conditions.
Finally, be aware that concat does not modify the array you call it on. It returns the concatenated array, so you need to assign that return value. In your code you don't assign the return value, so the call has no effect.
See also JavaScript ES6 promise for loop.
Probably you just need Promise.all method.
For every request you should create a promise and put it in an array, then you wrap everything in all method and you're done.
Example (assuming that getResults.get returns a promise):
let promiseChain = [];
for(let i = 0; i <5; i++){
promiseChain.push(getResults.get());
}
Promise.all(promiseChain)
.then(callback)
You can read more about this method here:
Promise.all at MDN
EDIT
You can access data returned by the promises this way:
function callback(data){
doSomething(data[0]) //data from the first promise in the chain
...
doEventuallySomethingElse(data[4]) //data from the last promise
}

NodeJS promise resolution

const a = [1, 2, 3, 4, 5];
const f = () => new Promise((resolve, reject) => resolve(4));
const g = () => {
Promise.all(a.map((member) => f().then((res) => res)))
.then((result) => {
console.log(result)
});
}
g();
Why do I not need another then attached to {return res;} here?
I read that when you have a return (something)inside a then, another then must be attached, but its not the case here. Help?
Promise.all expects an array of promises. .then returns a promise. Therefore your mapping logic converts an array of numbers to an array of promises, exactly what you need.
.then((res) => {return res;}) is completely unnecessary btw, return f(); would suffice. You can even simplify your current code further to:
Promise.all(a.map(f)).then(result => console.log(result));
I read that when you have a return (something) inside a then, another then must be attached
This has nothing to do with .then. .then simply returns a promise. To access the result of a promise you need to attach a handler via .then.
You don't need to do this here because you are passing the promises to Promise.all. You are accessing that result via .then((result)=>{console.log(result)}).
Why do I not need another then attached to {return res;} here?
I read that when you have a return (something)inside a then, another
then must be attached, but its not the case here. Help?
There is another .then() attached to Promise.all(). Did you mean .catch() should be attached to avoid Uncaught (in promise)?
Note also, Promise.all() is not returned from g() call, to further chain the Promise.
.then() and .catch() chained within .map() callback can be utilized to either handle error or rejected Promise and return a resolved Promise to .then() chained to Promise.all(); or to throw current or new Error() to Promise.all() call.
The pattern can also be used to return all promises passed to .map(), whether resolved or rejected to .then(), without immediately calling .catch() chained to Promise.all().
function f (index) {
return new Promise(function (resolve, reject) {
if (index !== 4) resolve(4);
else reject("err at index " + index)
})
}
var a =[1, 2, 3, 4, 5];
function g () {
return Promise.all(a.map((member, index)=>{
return f(index).then((res) => {return res;})
.catch(e => {console.log(e); throw new Error(e)})
}))
.then((result)=>{console.log(result); return result})
.catch(e => {
// handle error here, return resolved `Promise`,
// or `throw new Error(e)` to propagate error to
// `.catch()` chained to `g()` call
console.log("handle error within g", e);
return "error " + e.message + " handled"});
}
g()
.then(data => {console.log(data) /* `error err at 4 handled` */ })
.catch(e => console.log("done", e));

Categories