Promise in sequence does not stop when a promise reject - javascript

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);
});

Related

Promise.race() multiple resolved promises

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

Reject all functions if one is rejected

I have a similar problem in dialogflow fulfillment where I need to book any appointments on the Google calendar. I would reject all functions p1, p2 and p3 if only one of them is rejected. In the code below, although p2 is rejected, the others p1 and p3 are executed (I wish all functions p1, p2 and p3 were not performed).
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
});
}
function p1() {
new Promise((resolve, reject) => {
setTimeout(resolve, 1000, alert("one"));
});
}
function p2() {
new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
new Promise((resolve, reject) => {
setTimeout(resolve, 3000, alert("three"));
});
}
f1();
Well it contains a lot of code to implement it so I would give short instructions.
You need to have a way to reject your request or whatever you are doing. For examples with axious we can use CancelToken to cancel HTTP request.
Now you need to subscribe on each request and cancel the request or whatever you are using.
It is not clear what exactly you need. Functions can not be rejected or not executed if you run them in parallel. You can only notify the internal function code that the cancel operation was requested from the outside. We don't know what async operations are performed inside your async functions. Demo
import { CPromise } from "c-promise2";
function makePromise(ms, label) {
return new CPromise((resolve, reject, { onCancel }) => {
setTimeout(resolve, ms);
onCancel(() => console.log(`onCancel(${label})`));
});
}
CPromise.all([
makePromise(2000, "one"),
makePromise(1500, "two").then(() => {
throw Error("Oops");
}),
makePromise(4000, "three")
]).then(
(v) => console.log(`Done: ${v}`),
(e) => console.warn(`Fail: ${e}`)
);
onCancel(one)
onCancel(three)
Fail: Error: Oops
The problem is you're not returning anything from p1, p2, p3. So when you call Promise.all([p1(), p2(), p3()]) which is actually calling with Promise.all([undefined, undefined, undefined])(resolves anyway) which does not have a rejection. That's why you're not seeing the error.
Add return in your functions.
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
});
}
function p1() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
}
function p2() {
return new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
}
f1();
Remember Promises are not cancellable, if you really want cancel execution part, you can try something like this. I don't guarantee this works all the time and I don't think good idea to do in this way.
const timers = []
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
timers.forEach((timerId) => clearTimeout(timerId))
});
}
function p1() {
return new Promise((resolve, reject) => {
const timerId = setTimeout(() => {
alert(1)
resolve()
}, 1000);
timers.push(timerId)
});
}
function p2() {
return new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
return new Promise((resolve, reject) => {
const timerId = setTimeout(() => {
alert(2)
resolve()
}, 3000);
timers.push(timerId)
});
}
f1();

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];
}

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);

Why Promise.all cannot work after all asynchronous function are completed?

Hi I'm new to Js and I'd like to wait some async functions complete before print ni. But the code never print it I cannot understand why. Please help me:(
// public function
this.run = function() {
'use strict';
let compile_lib = lib_array.map((item) => {
return new Promise(() => {compileEntry(item);})
});
Promise.all(compile_lib).then(() => { console.log("ni"); });
}
The Promise.all(iterable) method returns a promise that resolves when
all of the promises in the iterable argument have resolved, or rejects
with the reason of the first passed promise that rejects.
In your example, you didn't resolve or reject your Promises:
let compile_lib = lib_array.map((item) => {
return new Promise((resolve, reject) => { compileEntry(item); })
});
please note the arguments resolve, reject. You should use these callbacks either to resolve or reject the promise. For example:
new Promise((resolve, reject) => {
var result = compileEntry(item);
if(result) {
resolve(result);
}else {
reject("reson");
}
})
You need to use resolve and reject
new Promise( /* executor */ function(resolve, reject) { ... } );
var lib_array = [1, 2, 3];
function compileEntry(item, index) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(console.log(item));
}, index * 1000);
});
}
this.run = function() {
'use strict';
let compile_lib = lib_array.map((item, index) => {
return compileEntry(item, index);
});
Promise.all(compile_lib).then(() => {
console.log("ni");
});
}
this.run();

Categories