Adding a promise to sequence if condition is met - javascript

I have a Promise, Promise A, which 'checks' some condition asynchronously. It is always resolved (no rejection), so only then is necessary. The resolve is either true or false (the aforementioned condition).
If the condition is not met (resolve(false)) in the first promise, a second promise, Final Promise is then executed. However, if the initial condition was met (resolve(true)), an additional promise is executed 'between the two': Promise B.
Because Final Promise produces two callbacks (then and catch), I am trying to avoid code duplication. This is what I'm using, which works, but the final callbacks are duplicated.
promiseA().then((condition) => {
if (condition) {
promiseB().then(finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
}));
} else {
finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});
}
});
I tried the following, but it doesn't work: Final Promise is being executed before Promise B.
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
}));
Additionally, I tried using the following, but then Final Promise wouldn't be executed after Promise B:
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise).then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});
I'm guessing I could store everything that is inside the else branch (in the first snippet) into a variable and use it for both cases. I'm wondering if there's a solution built into the Promise API.

I figured it out. The third snippet works, but I needed to 'manually' resolve Promise B to continue the chain (I learned something new!).
function promiseB() {
return new Promise((resolve) => {
console.log('Promise B execution');
resolve(); // adding this solved it
});
}
The queue snippet:
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise).then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});

Related

Error with JavaScript promise and then condition

I'm learning JavaScript promises and then, and am confused with this error using Node.js.
I would like dostart() to wait until nonblocking sleep is finished, and then return "Resolved" to the main function when it is done.
I get this error:
dostart().then(value => {
---------^
TypeError: Cannot read properties of undefined (reading 'then')
Help appreciated :)
function nonBlockingSleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function dostart() {
console.log("Hello2");
nonBlockingSleep(2000).then(() => {
console.log("Done");
return Promise.resolve("Resolved");
});
}
dostart().then(value => {
// main function - I'd like console.log to show "Resolved" when dostart() is finished
console.log(value);
})
dostart isn't async and doesn't return the promise from then, so you can't try to consume that promise where you call it.
You have at least a couple of options.
The simple one in this particular case is to return the promise from then:
function nonBlockingSleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function dostart() {
console.log("Hello2");
// *** Return the promise
return nonBlockingSleep(2000).then(() => {
console.log("Done");
// *** There's no need for `Promise.resolve` here, `then`
// _always_ returns a promise, and will use the return value
// here to fulfill it. (If you return a promise instead, it will
// resolve its promise to the promise you return instead of
// fulfilling its promise.)
return "Fulfilled";
});
}
dostart().then((value) => {
console.log(value);
});
Your other option is to use async/await:
function nonBlockingSleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// *** Make `dostart` an `async` function
async function dostart() {
console.log("Hello2");
// *** `await` the promise from `nonBlockingSleep`
await nonBlockingSleep(2000);
console.log("Done");
// *** Return the fulfillment value for this function's promise
return "Fulfilled";
}
dostart().then((value) => {
console.log(value);
});
Side note: I changed "Resolved" to "Fulfilled" because we're fulfilling the promise in that particular case. It's common, but incorrect, to use "resolve" for "fulfill." It's true that fulfilling a promise resolves it, but the converse isn't true — resolving a promise doesn't always fulfill it. It might leave it pending, or even eventually reject it, if you resolve it to another promise. I've written up promise terminology in this blog post.
since dostart returns nothing you can fix it like this:
function dostart() {
console.log("Hello2");
return nonBlockingSleep(2000).then(() => { console.log("Done"); return Promise.resolve("Resolved"); });
}
or use keyword async
async function dostart() {
console.log("Hello2");
return await nonBlockingSleep(2000).then(() => { console.log("Done"); return Promise.resolve("Resolved"); });
}

Promise chain continues after rejection

I'm having trouble to properly catch an error/reject in a promise chain.
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
}).then(p2).catch(() => {
console.log("Caught p2");
}).then(p3).catch(() => {
console.log("Caught p3");
}).then(() => {
console.log("Final then");
});
When the promise is rejected, the following .then still gets executed. In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
P1
P2
Caught p2
P3
Final then
The rejection gets caught correctly, but why is "P3" logged after the catch?
What am I doing wrong?
To clarify #evolutionxbox, this is my expected result:
Promise.resolve().then(() => {
console.log("resolve #1");
return Promise.reject();
}).then(() => {
console.log("resolve #2");
return Promise.resolve();
}).then(() => {
console.log("resolve #3");
return Promise.resolve();
}).then(() => {
console.log("Final end");
}).catch(() => {
console.log("Caught");
});
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
Here is a synchronous equivalent of your code:
const f1 = () => {
console.log("F1");
};
const f2 = () => {
console.log("F2");
throw new Error();
};
const f3 = () => {
console.log("F3");
};
try {
f1();
} catch {
console.log("Caught f1");
}
try {
f2();
} catch {
console.log("Caught f2");
}
try {
f3();
} catch {
console.log("Caught f3");
}
console.log("Final code");
As you can see, that gives a matching result. Hopefully, looking at the synchronous code you would not be surprised why. In a try..catch you are allowed to attempt recovery. The idea is that the catch will stop the error propagation and you can hopefully continue further. Or if you do want to stop, you still have to explicitly throw again, for example:
doCode();
try {
makeCoffee();
} catch(err) {
if (err instanceof IAmATeapotError) {
//attempt recovery
makeTea();
} else {
//unrecoverable - log and re-throw
console.error("Fatal coffee related issue encountered", err);
throw err;
}
}
doCode();
This is also the purpose Promise#catch() serves - so you can attempt recovery or at least act when there was a problem. The idea is that after the .catch() you might be able to continue:
const orderPizza = (topping) =>
new Promise((resolve, reject) => {
if (topping === "pepperoni")
reject(new Error("No pepperoni available"));
else
resolve(`${topping} pizza`);
});
const makeToast = () => "toast";
const eat = food => console.log(`eating some ${food}`);
async function main() {
await orderPizza("cheese")
.catch(makeToast)
.then(eat);
console.log("-----");
await orderPizza("pepperoni")
.catch(makeToast)
.then(eat);
}
main();
In order to reject the promise chain from a .catch() you need to do something similar as a normal catch and fail at the error recovery by inducing another error. You can throw or return a rejected promise to that effect.
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
The second piece of code you show fails entirely after a reject because there are no other .catch()-es that are successful. It is basically similar to this synchronous code:
try {
console.log("log #1");
throw new Error();
console.log("log #2");
console.log("log #3");
console.log("Final end");
} catch {
console.log("Caught");
}
Thus if you do not want to recover early, you can also skip the .catch() instead of inducing another error.
Try this.
const p1 = (arg) => {
// Promise returns data in the respected arguments
return new Promise((resolve, reject) => {
// Data to be accessed through first argument.
resolve(arg);
});
};
const p2 = (arg) => {
return new Promise((resolve, reject) => {
// Data to be accessed through second argument.
reject(arg);
});
}
p1('p1').then(resolve => {
console.log(resolve + ' is handled with the resolve argument. So it is accessed with .then()');
}) // Since reject isn't configured to pass any data we don't use .catch()
p2('p2').catch(reject => {
console.log(reject + ' is handled with the reject argument. So it is accessed with .catch()');
}) // Since resolve ins't configured to pass any data we don't use .then()
// You would normally configure a Promise to return a value on with resolve, and access it with .then() when it completes a task successfully.
// .catch() would then be chained on to the end of .then() to handle errors when a task cannot be completed.
// Here is an example.
const p3 = () => {
return new Promise((resolve, reject) => {
var condition = true;
if (condition === true) {
resolve('P3');
} else {
reject('Promise failed!');
}
});
};
p3('p3').then(resolve => {
console.log(resolve);
}).catch(reject => {
console.log(reject);
})
You do not do anything wrong.
In your code you call the first promise p1. Then you write p1.catch(...).then(...).then(...).then(...). This is a chain which means that you should call then 3 times, because you called resolve method in the p1 promise (all these thens depend on the first promise).
When the promise is rejected, the following .then still gets executed.
Yes. Just to be accurate: the then and catch method calls are all executed synchronously (in one go), and so all promises involved are created in one go. It's the callbacks passed to these methods that execute asynchronously, as the relevant promises resolve (fullfill or reject).
In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
This is not the case. The promise that a catch returns can either fullfill or reject depending on what happens in the callback passed to it, and so the callbacks further down the chain will execute accordingly when that promise resolves.
The rejection gets caught correctly, but why is "P3" logged after the catch?
As in your case the catch callback returns undefined (it only performs a console.log), its promise fullfulls! By consequence, the chained then callback -- on that promise -- is executed... etc.
If you want to "stop"
If you want to keep the chain as it is, but wish to have a behaviour where a rejection leads to no further execution of then or catch callbacks, then don't resolve the associated promise:
const stop = new Promise(resolve => null);
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
return stop; // don't resolve
}).then(p2).catch(() => {
console.log("Caught p2");
return stop;
}).then(p3).catch(() => {
console.log("Caught p3");
return stop;
}).then(() => {
console.log("Final then");
});

How to handle exception resulting in unresolved promise

I'm seeing some inconsistent behaviour when dealing with a promise that fails to resolve due to an unforeseen uncaught exception. It seems that depending on how I chain the promise changes whether this promise resolves and I don't understand why.
Here is the bad function:
function bad() {
return new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
}
If I call this function these ways it does not resolve:
bad().then(() => console.log('resolved')); // no console logging
try {
await bad();
} catch(e) {
console.log(e);
}
console.log('resolved'); // no console logging
But calling it like this does resolve:
Promise.resolve().then(bad()).then(() => console.log('resolved')); // console logs "resolved"
Why is this? Edit: I now understand what I was doing wrong. But what I really want to answer is the next part.
And how do I best protect myself against unforeseen exceptions when I have a chain of promises that need to be run serially and need to continue even if there is a failure somewhere along the chain?
I have also tried using catch or finally but they don't seem to make any difference. Once that unresolved promise is reached, execution fails.
The problem is that bad() throws an error asynchronously in such a way that the error can't be detected by the caller. If you want to throw an error inside a new Promise... segment, you should call the reject function:
function bad() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('bad!');
resolve();
}, 50);
});
}
bad()
.then(() => console.log('resolved'))
.catch((err) => console.log(err));
(async () => {
try {
await bad();
} catch(e) {
console.log(e);
}
console.log('await finished');
})();
The reason your
Promise.resolve().then(bad()).then
calls the next .then is because then accepts a function as a parameter, but your bad() is invoking bad in the beginning, while the interpreter is trying to come up with the Promise chain. If you had passed bad as the function parameter instead of calling it, you would see similar broken behavior as in your original code - the Promise would never resolve:
function bad() {
return new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
}
// Promise never resolves:
Promise.resolve().then(bad).then(() => console.log('resolved'));
In contrast, .then(bad()) will evaluate to a non-function, and hence that .then will resolve immediately, so the interpreter will go on to the next .then immediately as well.
In this code:
new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
The throw is happening in a non-async callback function. The way to handle something like this would be to use a try/catch statement on the code that could throw:
new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw 'unforseen exception!';
resolve();
}
catch (err) {
reject(err);
}
}, 50);
});

Rejecting a Promise by passing a Promise

Why is it exactly that the a resolveing promise correctly waits for the someOtherPromise to complete, but the reject does not? Run the following code sample and check the console.log output. I expected the "myFailingPromise rejected" message to show 2000 ms later, just as the "myPromise resolved" did.
let someOtherPromise = (previousPromise) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(previousPromise + ' => someOtherPromise after 2000ms');
resolve('someOtherPromise');
}, 2000);
});
}
let myPromise = () => {
return new Promise((resolve, reject) => {
resolve(someOtherPromise('myPromise'));
});
};
let myFailingPromise = () => {
return new Promise((resolve, reject) => {
reject(someOtherPromise('myFailingPromise'));
});
};
myPromise().then((val) => {
// this is executed after `someOtherPromise` resolves.
console.log('myPromise resolved');
}).catch((err) => {
console.log('myPromise rejected');
});
myFailingPromise().then((val) => {
// this is executed after `someOtherPromise` resolves.
console.log('myFailingPromise resolved');
}).catch((err) => {
console.log('myFailingPromise rejected');
});
I know the intended behaviour can be achieved by using someOtherPromise.then(reject) in the second example, but my question is why the promise as an argument to reject is not possible, since it works for resolve.
When you're resolving a promise A, if the value with which you're resolving it is a promise B, then your promise A will match the state of the promise B.
However, when you're rejecting a promise, your only giving a reason, whether the reason looks or not like a promise doesn't matter.
What you need to do, is to resolve with someOtherPromise in both cases.
If you want to wait for the first promise and reject anyway, you can do this:
let myFailingPromise = () => {
return new Promise((resolve, reject) => {
someOtherPromise.then(reject);
});
};
The reject only takes in a reason to highlight the error. Not another promise. You can resolve a promise with another promise of the same type, yet only the last promise's success case you will see.
Have a look at this adapted implementation:
const someOtherPromise = new Promise((resolve, _) => {
resolve("I am a success");
});
const failingPromise = new Promise((_, reject) => {
reject("I failed for a reason");
});
someOtherPromise
.then((result) => {
console.log("some other promise resolves", result);
failingPromise
.then((success) => {
console.log("never called");
})
.catch((reason) => {
console.error("failing promise rejects", reason);
});
}
)
.catch((error) => {
console.error("also never called", error);
});
This is the then-able approach to wait for other promises leading to a callback hell. This is why you can also use async / await syntax:
const app = async () => {
try {
const success1 = await someOtherPromise; // will succeed
console.log(success1);
const success2 = await failingPromise; // never succceds
console.log(success2); // never be reached
} catch (e) {
return Promise.reject(e); // catches the error of failing promise and rethrows it, redundant but here showcases error handling
}
}
;
app()
.then(() => {
console.log("never be reached because of failing promise");
})
.catch(console.error);
In regards to your updated question with the timeout, here is what you could do in order to always await another promise:
const otherPromise = async (willBeSuccesful: boolean) => {
console.log("started timer for case", willBeSuccesful);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("resolve timer for case", willBeSuccesful);
const successValue = "Fnord"; // whatever you want
return willBeSuccesful
? resolve(successValue)
: reject("this other promise failed because of reasons"); // only provide a reason, not another promise
});
};
};
const alwaysWaitForOtherPromiseThenRejectAnyway = async (otherPromise) => {
try {
const success = await otherPromise; // always waits 2 seconds, not matter
console.log("other promises succeeded with value", success);
} catch (e) {
return Promise.reject(e); // passing through reason, redundant, only to showcase
}
return Promise.reject("reason why this promise failed"); // only happens after otherPromise was resolved, you could swallow that error and fail here or resolve here as well
};
const succeedingPromise = otherPromise(true);
const failingPromise = otherPromise(false);
alwaysWaitForOtherPromiseThenRejectAnyway(succeedingPromise)
.catch((reason) => console.error(reason)); // fail after waiting for success of other promise
alwaysWaitForOtherPromiseThenRejectAnyway(failingPromise)
.catch((reason) => console.error(reason)); // will fail as soon as otherPromise fails
It will always wait for the timeout before rejection happens. Rejection will have different reasons. Output will be:
started timer for case true
started timer for case false
resolve timer for case true
resolve timer for case false
other promises succeeded with value Fnord
reason why this promise failed
this other promise failed because of reasons

Execute a promise loop and catch errors along the way

So here is a true beauty from #Roamer-1888:
executePromiseLoop(myArray).catch(logError).then(() => console.log('yay!'));
function executePromiseLoop(arr) {
return arr.reduce(function(promise, email) {
return promise.then(function() {
return myCustomPromise(email);
});
}, Promise.resolve());
}
It's a promise loop that executes serially. There are two concerns I have:
What happens if a promise in the loop fails, does it quit the loop?
Should I implement a catch inside the loop, or will a fail propagate back up to the calling function?
Should I insert a catch in the loop?
function executePromiseLoop(arr) {
return arr.reduce(function(promise, email) {
return promise.catch(logError).then(function() {
return myCustomPromise(email);
});
}, Promise.resolve());
}
What happens if a promise in the loop fails, does it quit the loop?
Yes. If a single promise is rejected, all the next promises will not be executed. See this code for example:
Promise.resolve()
.then(Promise.reject)
.then(() => console.log('this will not be executed'))
.catch(() => console.log('error'))
The promise in the third line will not be executed, because the promise before it was rejected.
Should I implement a catch inside the loop, or will a fail propagate back up to the calling function?
The reject message will propagate, so you don't need to use a catch inside the loop. See this code for example:
Promise.resolve()
.then(() => Promise.resolve())
.then(() => Promise.reject('something went wrong'))
.then(() => Promise.resolve())
.then(() => Promise.resolve())
.catch(error => console.log(error))

Categories

Resources