Promise.race() multiple resolved promises - javascript

The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
Taken from MDN site.
I have 5 promises and I need to know once any 2 promises are resolved, taking performance under consideration.
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
async function swimmer (name) {
const start = Date.now()
console.log(`${name} started the race`)
await sleep(Math.random() * 5000)
console.log(`${name} finished the race`)
return { name, delta: Date.now() - start }
}
const swimmers =
[ swimmer("Alice"), swimmer("Bob"), swimmer("Claire"), swimmer("David"), swimmer("Ed") ];
Promise.race(swimmers)
.then(({ name }) => console.log(`*** ${name} is the winner!!! ***`))
.catch(console.error)
This will return the fastest swimmer but I would like to print once I get 2 promises resolved.
How can I do it?

You could write a custom implementation of Promise.race that returns a promise that is resolved with the result of 2 promises that are resolved before others.
Following code example shows an implementation:
function customPromiseRace(promiseArr, expectedCount) {
return new Promise((resolve, reject) => {
if (promiseArr.length < expectedCount) {
throw new Error(`Not enough promises to get ${expectedCount} results`);
}
// array to store the results of fulfilled promises
const results = [];
for (const p of promiseArr) {
Promise.resolve(p).then(result => {
// push the promise fulfillment value to the "results"
// array only if we aren't already finished
if (results.length < expectedCount) {
results.push(result);
if (results.length === expectedCount) {
resolve(results);
}
}
}, reject);
}
});
}
Demo
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function swimmer(name) {
const start = Date.now();
console.log(`${name} started the race`);
await sleep(Math.random() * 5000);
console.log(`${name} finished the race`);
return { name, delta: Date.now() - start };
}
const swimmers = [
swimmer('Alice'),
swimmer('Bob'),
swimmer('Claire'),
swimmer('David'),
swimmer('Ed'),
];
function customPromiseRace(promiseArr, expectedCount) {
return new Promise((resolve, reject) => {
if (promiseArr.length < expectedCount) {
throw new Error(`Not enough promises to get ${expectedCount} results`);
}
const results = [];
for (const p of promiseArr) {
Promise.resolve(p).then(result => {
if (results.length < expectedCount) {
results.push(result);
if (results.length === expectedCount) {
resolve(results);
}
}
}, reject);
}
});
}
customPromiseRace(swimmers, 2).then(console.log).catch(console.error);

You could use .then(handleMyPromise) inside of Promise.race.
Example:
let handlepromise1 = function ( response ){
console.log("Expected response: ",response);
return response;
}
let promise1 = function () {
return new Promise((r) => {
setTimeout(()=>{
console.log("Promise1 says");
r("promise1")
},2000)
})
}
let timeout = function(){
return new Promise((r) => {
setTimeout(()=>{
console.log("timeout says");
r("timeout")
},3000)
});
};
(async function(){
let func = async function tt(){
let r = await Promise.race([
promise1().then(handlepromise1),
timeout()
])
console.log("Result: ",r);
}
func()
})()
Result of console:
Promise1 says
Expected response: promise1
Result: promise1
timeout says

Related

How to implement Promise.race() with async/await

How can I implement Promise.race() method with async and await?
async function promiseRace(promises) {
const results = [];
for (const p of promises) {
await p;
results.push(p);
}
return results[0];
}
I've tried to implement it like above but this doesn't work.
You can't. When using await you halt execution onto one specific promise. To implement Promise.race manually, you have to fiddle with callbacks:
function race(promises) {
return new Promise((resolve, reject) => {
for(const promise of promises)
promise.then(resolve, reject);
});
}
You can't. Just like you cannot implement the Promise constructor using async/await. Remember that await is only syntactic sugar for then calls - and you cannot implement the basic promise combinators using only that.
You can by using a wrapper promise along with async await. In the wrapper promise, protect the resolve/reject so that only the first promise wins.
Promise.race example using async/await:
// Implements promise.race
const race = async (promises) => {
// Create a promise that resolves as soon as
// any of the promises passed in resolve or reject.
const raceResultPromise = new Promise((resolve, reject) => {
// Keep track of whether we've heard back from any promise yet.
let resolved = false;
// Protect the resolve call so that only the first
// promise can resolve the race.
const resolver = (promisedVal) => {
if (resolved) {
return;
}
resolved = true;
resolve(promisedVal);
};
// Protect the rejects too because they can end the race.
const rejector = (promisedErr) => {
if (resolved) {
return;
}
resolved = true;
reject(promisedErr);
};
// Place the promises in the race, each can
// call the resolver, but the resolver only
// allows the first to win.
promises.forEach(async (promise) => {
try {
const promisedVal = await promise;
resolver(promisedVal);
} catch (e) {
rejector(e);
}
});
});
return raceResultPromise;
};
// *************
// Test Methods
// *************
const fetch = async (millis) => {
await waitMillis(millis);
return 'Async result: ' + millis + ' millis.';
};
const waitMillis = (millis) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, millis);
});
};
const run = async () => {
let result;
result = await race([fetch(1), fetch(2), fetch(3)]);
console.log('Winner', result);
result = await race([fetch(3), fetch(2), fetch(1)]);
console.log('Winner', result);
result = await race([fetch(10), fetch(3), fetch(4)]);
console.log('Winner', result);
};
run();
Why not:
const race = async (promiseArr) => {
return Promise.race(promiseArr)
}
And inside your async function:
let dayAtTheRace = await race([
my.promiseFunc(),
my.wait(10)
])
function promiseRace(promises) {
return new Promise((resolve, reject) => {
promises.forEach(async (promise) => {
try {
const result = await promise;
resolve(result);
} catch (err) {
reject(err);
}
});
});
Here is my solution:
async function promiseRace(promises) {
const results = [];
for (const p of promises) {
results.push(await p);
}
return results[0];
}

run a callback function after forEach loop finish

how to callback timer() function after forEach loop is finished, using the same code. or is there is a better way to loop through each user with delay then after the loop is done, the timer() is called back using forEach.
const usersProfile = () => {
let interval = 1000;
let promise = Promise.resolve();
db.select('*').from('users')
.returning('*')
.then(users => {
users.forEach((user, index) => {
setTimeout(function(){
}, index * 1000)
db.select('*').from(`${user.name}`)
.returning('*')
.then(userdata => {
userdata.forEach(data => {
promise = promise.then(function(){
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`)
}
return new Promise(function(resolve){
setTimeout(function(){
resolve();
}, interval)
})
})
})
})
})
timer();
})
}
const timer = () => {
setTimeout(usersProfile, 1000)
}
timer();
===============ALL THE ABOVE ARE MY OLD CODE ================
but thanks to https://stackoverflow.com/users/2404452/tho-vu it solved most of the problem but can i do this to serve the purpose of the app
const usersProfile = async () => {
let interval = 1000;
const delayPromise = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`);
resolve();
}
}, delayDuration)
});
};
const userInfo = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${data.info}`);//info about user from his table each user has his own table besides users table that has only the user tables
resolve();
}, delayDuration)
});
};
try {
const userData = await db.select('*').from('users').returning('*');
const promises = userData.map((data, index) => delayPromise(data, index * interval));
const userData = await db.select('*').from(`${data.name}`).returning('*');
const info = userData.map((data, index) => userInfo(data, index * interval));
await Promise.all(promises);
// here you can write your effects
} catch (e) {
console.log(e);
}
}
Another approach using async-await to avoid callback hell.
const usersProfile = async () => {
let interval = 1000;
const delayPromise = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`);
resolve();
}
}, delayDuration)
});
};
try {
const userData = await db.select('*').from('users').returning('*');
const promises = userData.map((data, index) => delayPromise(data, index * interval));
await Promise.all(promises);
// here you can write your effects
} catch (e) {
console.log(e);
}
}
it is hard for me to fully understand what you wanted to accomplish through the code, but I will try to explain what you can do to solve the issue.
First of all, if you want to wait for multiple promises, you should use Promise.all and not promise = promise.then
You can do this as so:
let promises = [];
users.forEach((user, index) => {
let promise = db.select(*).from('users') //should return a promise
promises.push(promise);
});
//promises have already started to execute (in parallel)
Promise.all(promises)
.then(() => console.log('all promises finished successfully'))
.catch((e) => console.error('received error at one of the promises'));
// when the program arrives here we know for a fact that either all promises executed successfully or one of the promises failed
timer();
Explanation: because promises execute asynchronously the forEach() function does not wait for any of the promises created to finish, so the solution is to wait for all of them at then after the entire loop.
This means the promises are created, and are being executed while the forEach() loop executes, in parallel, and when the program reaches Promise.all it stops and waits for all of them to finish.
Second, I would strongly advice you to use functions as a way to simplify your code, that way it would be easier for you to grasp every thing that is happening, even if its complicated.
Good luck !

Why does async array map return promises, instead of values

See the code below
var arr = await [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
console.log(arr); // <-- [Promise, Promise, Promise ....]
// i would expect it to return [1,2,3,4,5]
Quick edit:
The accepted answer is correct, by saying that map doesnt do anything special to async functions. I dont know why i assumed it recognizes async fn and knows to await the response.
I was expecting something like this, perhaps.
Array.prototype.mapAsync = async function(callback) {
arr = [];
for (var i = 0; i < this.length; i++)
arr.push(await callback(this[i], i, this));
return arr;
};
var arr = await [1,2,3,4,5].mapAsync(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
// outputs 1, 2 ,3 ... with 1 second intervals,
// arr is [1,2,3,4,5] after 5 seconds.
Because an async function always returns a promise; and map has no concept of asynchronicity, and no special handling for promises.
But you can readily wait for the result with Promise.all:
try {
const results = await Promise.all(arr);
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
(async() => {
try {
console.log(await Promise.all(arr));
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
})();
.as-console-wrapper {
max-height: 100% !important;
}
or using Promise syntax
Promise.all(arr)
.then(results => {
// Use `results`, which will be an array
})
.catch(err => {
// Handle error
});
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Promise.all(arr)
.then(results => {
console.log(results);
})
.catch(err => {
// Handle error
});
.as-console-wrapper {
max-height: 100% !important;
}
Side note: Since async functions always return promises, and the only thing you're awaiting in your function is a promise you create, it doesn't make sense to use an async function here anyway. Just return the promise you're creating:
var arr = [1,2,3,4,5].map((index) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Of course, if you're really doing something more interesting in there, with awaits on various things (rather than just on new Promise(...)), that's different. :-)
Since it is async, the values have not been determined at the time map returns. They won't exist until the arrow function has been run.
This is why Promises exist. They are a promise of a value being available in the future.

Promise in sequence does not stop when a promise reject

I have a list of promises.
var p1 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise 1 done");
}, 2000);
})
}
};
var p2 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("Promise 2 reject");
}, 1000);
})
}
};
var p3 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise 3 done");
}, 1500);
})
}
};
I want to execute [p1,p2,p3] in sequence. I writed a function Process.sequence to act like Promise.all() (resolve when all promises resolve, and reject right after a promise rejects)
Process = {
sequence: function(promises){
window.promises = promises;
return new Promise(function (resolve, reject) {
promises.reduce(function (sequence, promise) {
return sequence.then(function () {
return promise.run();
}).then(function (result) {
console.log(result);
if (promises.indexOf(promise) == promises.length - 1) {
resolve("All Done");
}
}).catch(function (reason) {
reject(reason);
});
}, Promise.resolve());
});
}
};
But when i run Process.sequence...
Process.sequence([p1,p2,p3]).then(function(result){
console.log(result);
}, function(reason){
console.log(reason);
});
... the p3 still executed even p2 had rejected before.
Here is the result i expect:
Promise 1 done
Promise 2 reject
But this is the real result:
Promise 1 done
Promise 2 reject
Promise 3 done
What wrong with my function Process.sequence?
UPDATE
Thank #JaromandaX for your support. The function Process.sequence should be like this.
Process = {
sequence: function(promises) {
return promises.reduce(function(sequence, promise) {
return sequence.then(function() {
return promise.run();
}).then(function(result) {
console.log(result);
});
}, Promise.resolve()).then(function() {
return "All Done";
});
}
};
As you want the results to contain all of the fulfilled values, and the promises only to be created ("run") when all previous ones were fulfilled, you should make some changes:
Make your loop asynchronous, as you can only know whether to continue with the next promise or not when the previous one has resolved.
Stop looping when a promise rejects
Concatenate the results in an array as you progress
Furthermore, I would not call a variable "promise" when it is not a promise object... that will only bring confusion. Call it task or something. The promise here is what the task.run() method returns.
Here is how I would suggest to do it:
// The p1, p2, p3 objects have been written more concisely using a helper function:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };
const Process = {
sequence: function (tasks) {
return (function loop(results) {
return results.length >= tasks.length
// All promises were fulfilled: return the results via a promise
? Promise.resolve(results)
// Create the next promise
: tasks[results.length].run()
// When it fulfills, collect the result, and chain the next promise
.then(value => loop(results.concat(value)))
// When it rejects, return a rejected promise with
// the partial results and the reason of rejection
.catch(reason => { throw results.concat(reason) });
})([]); // Start with an empty results array
}
};
console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
console.log('Fulfilled: ', result);
}).catch(function(reason){
console.log('Rejected: ', reason);
});
As browsers have started to support async/await you could also use this more procedural looking code:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };
const Process = {
sequence: async function (tasks) {
const results = [];
for (let task of tasks) {
try {
results.push(await task.run());
} catch(err) {
throw results.concat(err);
}
}
return results;
}
};
console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
console.log('Fulfilled: ', result);
}).catch(function(reason){
console.log('Rejected: ', reason);
});

How to structure nested Promises

I have a situation where I think the only choice for me is to nest some Promises within each other. I have a Promise that needs to be performed and a method that does something until that Promise is complete. Something like this:
let promise = new Promise((resolve, reject) => {
// Do some stuff
});
doSomethingUntilPromiseisDone(promise);
However, within my Promise, I need to execute another method that returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
// Do something here
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
But now, in the fetchValue method's then statement, I have another method I need to execute that, guess what, returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
saveToCache(value)
.then((success) => {
console.log('success!!');
resolve('success');
});
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
So in the end, I have a Promise, within a Promise, within a Promise. Is there someway I can structure this better so that it is more straightforward? It seems like nesting them within each other is counter to Promise's intended chaining approach.
Use .then()
let doStuff = (resolve, reject) => {/* resolve() or reject() */};
let promise = new Promise(doStuff);
doSomethingUntilPromiseisDone(
promise
.then(value => fetchValue(url))
.then(value => value.blob())
.then(saveToCache)
)
.then(success => console.log("success!!"))
.catch(err => console.error(err))
you can use generator to flatten your nested promises (Bluebird.couroutine or Generators)
//Bluebird.couroutine
const generator = Promise.coroutine(function*() {
try {
const value = yield fetchValue(url);
const success = yield saveToCache(value);
console.log('success:', success);
} catch(e) {
console.error(err);
}
}));
generator();
Each function will call the next one with the result of the method before.
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);

Categories