Node Unhandled Promise issue - javascript

I previously asked the question found here:
Retaining Data across multiple promise chains
I ended up using T.J. Crowder's answer for my base code and have made many many changes since. But I noticed something weird in node which I cannot seem to overcome. I went back to the base code he provided and the issue seems to be there as well.
Here is the example:
"use strict";
// For tracking our status
class Status {
constructor(total = 0, count = 0) {
this.id = ++Status.id;
this.total = total;
this.count = count;
}
addCall() {
++this.total;
return this;
}
addProgress() {
++this.count;
return this;
}
toString() {
return `[S${this.id}]: Total: ${this.total}, Count: ${this.count}`;
}
}
Status.id = 0;
// The promise subclass
class RepoPromise extends Promise {
constructor(executor) {
super(executor);
this.s = new Status();
}
// Utility method to wrap `then`/`catch` callbacks so we hook into when they're called
_wrapCallbacks(...callbacks) {
return callbacks.filter(c => c).map(c => value => this._handleCallback(c, value));
}
// Utility method for when the callback should be called: We track that we've seen
// the call then execute the callback
_handleCallback(callback, value) {
this.s.addProgress();
console.log("Progress: " + this.s);
return callback(value);
}
// Standard `then`, but overridden so we track what's going on, including copying
// our status object to the new promise before returning it
then(onResolved, onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.then(...this._wrapCallbacks(onResolved, onRejected));
newPromise.s = this.s;
return newPromise;
}
// Standard `catch`, doing the same things as `then`
catch(onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.catch(...this._wrapCallbacks(onRejected));
newPromise.s = this.s;
return newPromise;
}
}
// Create a promise we'll resolve after a random timeout
function delayedGratification() {
return new Promise(resolve => {
setTimeout(_ => {
resolve();
}, Math.random() * 1000);
});
}
// Run! Note we follow both kinds of paths: Chain and diverge:
const rp = RepoPromise.resolve('Test');
rp.then(function(scope) {
return new Promise((resolve, reject) => {
console.log(' Rejected')
reject(scope)
})
})
.catch(e => {console.log('Never Makes it')})
when I run this with: node test.js I get the following output
Added: [S1]: Total: 1, Count: 0
Added: [S1]: Total: 2, Count: 0
Added: [S1]: Total: 3, Count: 0
Progress: [S1]: Total: 3, Count: 1
Rejected
(node:29364) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Test
(node:29364) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Notice that the console log for "never makes it" is not present, also note, I already fixed the issue with catch running twice as it is simple syntactic sugar for then(null, function(){}), so you can ignore that.
Why is catch not working as I expect? When I do this with a normal promise, there are no issues, like below. So I know for a fact that the _wrapCallbacks is causing the issue, I am just not sure why, or how to fix it.
const rp = Promise.resolve('Test');
rp.then(function(scope) {
return new Promise((resolve, reject) => {
console.log(' Rejected')
reject(scope)
})
})
.catch(e => {console.log('Makes it')})

The catch implementation of your promise doesn't work. Notice that the native catch is implemented as return this.then(null, callback) - calling super.catch will just direct back to your then implementation.
And your then implementation has a major fault: it doesn't like to get a null argument before a function. Observe what happens in the above call when you're doing this:
_wrapCallbacks(...callbacks) {
return callbacks.filter(c => c).map(…);
// ^^^^^^^^^^^^^^^
}
then(onResolved, onRejected) {
…
const newPromise = super.then(...this._wrapCallbacks(onResolved, onRejected));
…
}
That'll simply remove the null from the array of arguments and pass the onrejected callback as onfulfilled instead. You'll want to drop the filter and use a ternary in the mapping function instead:
_wrapCallbacks(...callbacks) {
return callbacks.map(c => typeof c == "function"
? value => this._handleCallback(c, value)
: c);
}
Also you can just drop the overridden catch.

Related

Handle a reject in promise.all() in javascript [duplicate]

I have an array of Promises that I'm resolving with Promise.all(arrayOfPromises);
I go on to continue the promise chain. Looks something like this
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
I want to add a catch statement to handle an individual promise in case it errors, but when I try, Promise.all returns the first error it finds (disregards the rest), and then I can't get the data from the rest of the promises in the array (that didn't error).
I've tried doing something like ..
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler()
.then(function(data) {
return data;
})
.catch(function(err) {
return err
});
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
But that doesn't resolve.
Thanks!
--
Edit:
What the answers below said were completely true, the code was breaking due to other reasons. In case anyone is interested, this is the solution I ended up with ...
Node Express Server Chain
serverSidePromiseChain
.then(function(AppRouter) {
var arrayOfPromises = state.routes.map(function(route) {
return route.async();
});
Promise.all(arrayOfPromises)
.catch(function(err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
// full array of resolved promises;
})
};
API Call (route.async call)
return async()
.then(function(result) {
// dispatch a success
return result;
})
.catch(function(err) {
// dispatch a failure and throw error
throw err;
});
Putting the .catch for Promise.all before the .then seems to have served the purpose of catching any errors from the original promises, but then returning the entire array to the next .then
Thanks!
Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.
Some libraries have something called Promise.when, which I understand would instead wait for all promises in the array to either resolve or reject, but I'm not familiar with it, and it's not in ES6.
Your code
I agree with others here that your fix should work. It should resolve with an array that may contain a mix of successful values and errors objects. It's unusual to pass error objects in the success-path but assuming your code is expecting them, I see no problem with it.
The only reason I can think of why it would "not resolve" is that it's failing in code you're not showing us and the reason you're not seeing any error message about this is because this promise chain is not terminated with a final catch (as far as what you're showing us anyway).
I've taken the liberty of factoring out the "existing chain" from your example and terminating the chain with a catch. This may not be right for you, but for people reading this, it's important to always either return or terminate chains, or potential errors, even coding errors, will get hidden (which is what I suspect happened here):
Promise.all(state.routes.map(function(route) {
return route.handler.promiseHandler().catch(function(err) {
return err;
});
}))
.then(function(arrayOfValuesOrErrors) {
// handling of my array containing values and/or errors.
})
.catch(function(err) {
console.log(err.message); // some coding error in handling happened
});
NEW ANSWER
const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));
FUTURE Promise API
Chrome 76: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
You can download https://www.npmjs.com/package/promise.allsettled to get it now. In certain browsers allSettled comes preinstalled with the browser itself. It's worth downloading the package for peace of mind because eg. TypeScript doesn't have default definitions for allSettled.
ES2020 introduces new method for the Promise type: Promise.allSettled().
Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.
async function() {
const promises = [
fetch('/api.stackexchange.com/2.2'), // succeeds
fetch('/this-will-fail') // fails
];
const result = await Promise.allSettled(promises);
console.log(result.map(promise => promise.status));
// ['fulfilled', 'rejected']
}
Read more in the v8 blog post.
To continue the Promise.all loop (even when a Promise rejects) I wrote a utility function which is called executeAllPromises. This utility function returns an object with results and errors.
The idea is that all Promises you pass to executeAllPromises will be wrapped into a new Promise which will always resolve. The new Promise resolves with an array which has 2 spots. The first spot holds the resolving value (if any) and the second spot keeps the error (if the wrapped Promise rejects).
As a final step the executeAllPromises accumulates all values of the wrapped promises and returns the final object with an array for results and an array for errors.
Here is the code:
function executeAllPromises(promises) {
// Wrap all Promises in a Promise that will always "resolve"
var resolvingPromises = promises.map(function(promise) {
return new Promise(function(resolve) {
var payload = new Array(2);
promise.then(function(result) {
payload[0] = result;
})
.catch(function(error) {
payload[1] = error;
})
.then(function() {
/*
* The wrapped Promise returns an array:
* The first position in the array holds the result (if any)
* The second position in the array holds the error (if any)
*/
resolve(payload);
});
});
});
var errors = [];
var results = [];
// Execute all wrapped Promises
return Promise.all(resolvingPromises)
.then(function(items) {
items.forEach(function(payload) {
if (payload[1]) {
errors.push(payload[1]);
} else {
results.push(payload[0]);
}
});
return {
errors: errors,
results: results
};
});
}
var myPromises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(new Error('3')),
Promise.resolve(4),
Promise.reject(new Error('5'))
];
executeAllPromises(myPromises).then(function(items) {
// Result
var errors = items.errors.map(function(error) {
return error.message
}).join(',');
var results = items.results.join(',');
console.log(`Executed all ${myPromises.length} Promises:`);
console.log(`— ${items.results.length} Promises were successful: ${results}`);
console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});
Promise.allSettled
Instead of Promise.all use Promise.allSettled which waits for all promises to settle, regardless of the result
let p1 = new Promise(resolve => resolve("result1"));
let p2 = new Promise( (resolve,reject) => reject('some troubles') );
let p3 = new Promise(resolve => resolve("result3"));
// It returns info about each promise status and value
Promise.allSettled([p1,p2,p3]).then(result=> console.log(result));
Polyfill
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises
.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
As #jib said,
Promise.all is all or nothing.
Though, you can control certain promises that are "allowed" to fail and we would like to proceed to .then.
For example.
Promise.all([
doMustAsyncTask1,
doMustAsyncTask2,
doOptionalAsyncTask
.catch(err => {
if( /* err non-critical */) {
return
}
// if critical then fail
throw err
})
])
.then(([ mustRes1, mustRes2, optionalRes ]) => {
// proceed to work with results
})
Using Async await -
here one async function func1 is returning a resolved value, and func2 is throwing a error and returning a null in this situation, we can handle it how we want and return accordingly.
const callingFunction = async () => {
const manyPromises = await Promise.all([func1(), func2()]);
console.log(manyPromises);
}
const func1 = async () => {
return 'func1'
}
const func2 = async () => {
try {
let x;
if (!x) throw "x value not present"
} catch(err) {
return null
}
}
callingFunction();
Output is - [ 'func1', null ]
if you get to use the q library https://github.com/kriskowal/q
it has q.allSettled() method that can solve this problem
you can handle every promise depending on its state either fullfiled or rejected
so
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
//do somthing
} else {
// do something else
}
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
For those using ES8 that stumble here, you can do something like the following, using async functions:
var arrayOfPromises = state.routes.map(async function(route){
try {
return await route.handler.promiseHandler();
} catch(e) {
// Do something to handle the error.
// Errored promises will return whatever you return here (undefined if you don't return anything).
}
});
var resolvedPromises = await Promise.all(arrayOfPromises);
Promise.allSettled with a filter
const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.
const resultFilter = (result, error) => result.filter(i => i.status === (!error ? 'fulfilled' : 'rejected')).map(i => (!error ? i.value : i.reason));
const result = await Promise.allSettled(promises);
const fulfilled = resultFilter(result); // all fulfilled results
const rejected = resultFilter(result, true); // all rejected results
Have you considered Promise.prototype.finally()?
It seems to be designed to do exactly what you want - execute a function once all the promises have settled (resolved/rejected), regardless of some of the promises being rejected.
From the MDN documentation:
The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.
The finally() method is very similar to calling .then(onFinally, onFinally) however there are couple of differences:
When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it.
A finally callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there's no need to provide it.
Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.
Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be fulfilled with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.
== Fallback ==
If your version of JavaScript doesn't support Promise.prototype.finally() you can use this workaround from Jake Archibald: Promise.all(promises.map(p => p.catch(() => undefined)));
We can handle the rejection at the individual promises level, so when we get the results in our result array, the array index which has been rejected will be undefined. We can handle that situation as needed, and use the remaining results.
Here I have rejected the first promise, so it comes as undefined, but we can use the result of the second promise, which is at index 1.
const manyPromises = Promise.all([func1(), func2()]).then(result => {
console.log(result[0]); // undefined
console.log(result[1]); // func2
});
function func1() {
return new Promise( (res, rej) => rej('func1')).catch(err => {
console.log('error handled', err);
});
}
function func2() {
return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
let sum = 0;
let promiseErrorArr = [];
Promise.allSettled(promises)
.then((results) => {
results.forEach(result => {
if (result.status === "rejected") {
sum += 1;
promiseErrorArr.push(result)
}
})
return ( (sum>0) ? promiseFailed() : promisePassed())
})
function promiseFailed(){
console.log('one or all failed!')
console.log(promiseErrorArr)
}
function promisePassed(){
console.log('all passed!')
}
// expected output:
// "one or all failed!"
// Array [Object { status: "rejected", reason: "foo" }]
Alternately, if you have a case where you don't particularly care about the values of the resolved promises when there is one failure but you still want them to have run, you could do something like this which will resolve with the promises as normal when they all succeed and reject with the failed promises when any of them fail:
function promiseNoReallyAll (promises) {
return new Promise(
async (resolve, reject) => {
const failedPromises = []
const successfulPromises = await Promise.all(
promises.map(
promise => promise.catch(error => {
failedPromises.push(error)
})
)
)
if (failedPromises.length) {
reject(failedPromises)
} else {
resolve(successfulPromises)
}
}
)
}
You can always wrap your promise returning functions in a way that they catches failure and returning instead an agreed value (e.g. error.message), so the exception won't roll all the way up to the Promise.all function and disable it.
async function resetCache(ip) {
try {
const response = await axios.get(`http://${ip}/resetcache`);
return response;
}catch (e) {
return {status: 'failure', reason: 'e.message'};
}
}
I've found a way (workaround) to do this without making it sync.
So as it was mentioned before Promise.all is all of none.
so... Use an enclosing promise to catch and force resolve.
let safePromises = originalPrmises.map((imageObject) => {
return new Promise((resolve) => {
// Do something error friendly
promise.then(_res => resolve(res)).catch(_err => resolve(err))
})
})
})
// safe
return Promise.all(safePromises)
You would need to know how to identify an error in your results. If you do not have a standard expected error, I suggest that you run a transformation on each error in the catch block that makes it identifiable in your results.
try {
let resArray = await Promise.all(
state.routes.map(route => route.handler.promiseHandler().catch(e => e))
);
// in catch(e => e) you can transform your error to a type or object
// that makes it easier for you to identify whats an error in resArray
// e.g. if you expect your err objects to have e.type, you can filter
// all errors in the array eg
// let errResponse = resArray.filter(d => d && d.type === '<expected type>')
// let notNullResponse = resArray.filter(d => d)
} catch (err) {
// code related errors
}
Not the best way to error log, but you can always set everything to an array for the promiseAll, and store the resulting results into new variables.
If you use graphQL you need to postprocess the response regardless and if it doesn't find the correct reference it'll crash the app, narrowing down where the problem is at
const results = await Promise.all([
this.props.client.query({
query: GET_SPECIAL_DATES,
}),
this.props.client.query({
query: GET_SPECIAL_DATE_TYPES,
}),
this.props.client.query({
query: GET_ORDER_DATES,
}),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;
Unfortunately, I don't have enough reputation to comment (or do much of anything, really), so I'm posting this as an answer in response to Eric's answer here.
The executor function can also be an async function. However, this is usually a mistake, for a few reasons:
If an async executor function throws an error, the error will be lost and won’t cause the newly-constructed Promise to reject. This could make it difficult to debug and handle some errors.
If a Promise executor function is using await, this is usually a sign that it is not actually necessary to use the new Promise constructor, or the scope of the new Promise constructor can be reduced.
From this explanation as to why Promises should not utilize an async executor function
Instead, you should opt for Promise.allSettled(), as suggested here by Asaf.
With the help of allSettled,we can now read the status of
each promise is, and process each error individually, without losing any of this critical information
const promises = [
fetch('/api/first'), // first
fetch('/api/second') // second
];
The simplest way is to handle errors
const [firstResult, secondResult] = await Promise.allSettled(promises)
// Process first
if (firstResult.status === 'rejected') {
const err = firstResult.reason
// Here you can handle error
} else {
const first = firstResult.value
}
// Process second
if (secondResult.status === 'rejected') {
const err = secondResult.reason
// Here you can handle error
} else {
const second = secondResult.value
}
A nice way to handle error
const results = await Promise.allSettled(promises);
const [first, second] = handleResults(results)
function handleResults(results) {
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)
if (errors.length) {
// Aggregate all errors into one
throw new AggregateError(errors)
}
return results.map(result => result.value)
}
That's how Promise.all is designed to work. If a single promise reject()'s, the entire method immediately fails.
There are use cases where one might want to have the Promise.all allowing for promises to fail. To make this happen, simply don't use any reject() statements in your promise. However, to ensure your app/script does not freeze in case any single underlying promise never gets a response, you need to put a timeout on it.
function getThing(uid,branch){
return new Promise(function (resolve, reject) {
xhr.get().then(function(res) {
if (res) {
resolve(res);
}
else {
resolve(null);
}
setTimeout(function(){reject('timeout')},10000)
}).catch(function(error) {
resolve(null);
});
});
}
I wrote a npm library to deal with this problem more beautiful.
https://github.com/wenshin/promiseallend
Install
npm i --save promiseallend
2017-02-25 new api, it's not break promise principles
const promiseAllEnd = require('promiseallend');
const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};
// input promises with array
promiseAllEnd(promises, {
unhandledRejection(error, index) {
// error is the original error which is 'error'.
// index is the index of array, it's a number.
console.log(error, index);
}
})
// will call, data is `[1, undefined, 2]`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// input promises with object
promiseAllEnd(promisesObj, {
unhandledRejection(error, prop) {
// error is the original error.
// key is the property of object.
console.log(error, prop);
}
})
// will call, data is `{k1: 1, k3: 2}`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
// won't call
.then(data => console.log(data))
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
// will call, data is `[1, undefined, 2]`.
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
————————————————————————————————
Old bad api, do not use it!
let promiseAllEnd = require('promiseallend');
// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
.then(data => console.log(data)) // [1, undefined, 2]
.catch(error => console.log(error.errorsByKey)) // {1: 'error'}
// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
.then(data => console.log(data)) // {k1: 1, k3: 2}
.catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

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

Simple Promise and Then implementation

Recently I was shown a piece of code that were asked during a full-stack developer interview.
It involved creating a Promise, in which the candidate should implement,
passing it a resolve function, and chaining 2 then's.
I tried implementing the Promise very naively only to make the code work.
Created a ctor that accepts a resolver func,
Created a Then function that accepts a callback and returns a Promise,
and simply calls the callback on the resolver function.
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
const result = new MyPromise(callback);
this.resolver(callback);
return result;
}
}
promise = new MyPromise(
(result) => {
setTimeout(result(2), 500);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
The expected result is 2,4 - just like operating with real Promise.
But i'm getting 2,2.
I'm having trouble figuring out how to get the return value for the first "then" and passing it along.
Here's the shortened code for creating a promise class,
class MyPromise {
constructor(executor) {
this.callbacks = [];
const resolve = res => {
for (const { callback } of this.callbacks) {
callback(res);
}
};
executor(resolve);
}
then(callback) {
return new MyPromise((resolve) => {
const done = res => {
resolve(callback(res));
};
this.callbacks.push({ callback: done });
});
}
}
promise = new MyPromise((resolve) => {
setTimeout(() => resolve(2), 1000);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
Your question has some issues:
The r2 variable is nowhere defined. I will assume result was intended.
The setTimeout is doing nothing useful, since you execute result(2) immediately. I will assume setTimeout(() => result(2), 500) was intended.
If the code was really given like that in the interview, then it would be your job to point out these two issues before doing anything else.
One issue with your attempt is that the promise returned by the then method (i.e. result) is never resolved. You need to resolve it as soon as the this promise is resolved, with the value returned by the then callback.
Also, the promise constructor argument is a function that should be executed immediately.
In the following solution, several simplifications are made compared to a correct Promise behaviour.
It does not call the then callbacks asynchronously;
It does not support multiple then calls on the same promise;
It does not provide the rejection path;
It does not prevent a promise from resolving twice with a different value;
It does not deal with the special case where a then callback returns a promise
console.log("Wait for it...");
class MyPromise {
constructor(executor) {
executor(result => this.resolve(result));
}
resolve(value) {
this.value = value;
this.broadcast();
}
then(onFulfilled) {
const promise = new MyPromise(() => null);
this.onFulfilled = onFulfilled;
this.resolver = (result) => promise.resolve(result);
this.broadcast();
return promise;
}
broadcast() {
if (this.onFulfilled && "value" in this) this.resolver(this.onFulfilled(this.value));
}
};
// Code provided by interviewer, including two corrections
promise = new MyPromise(
(result) => {
setTimeout(()=>result(2), 500); // don't execute result(2) immediately
});
promise.then(result => {
console.log(result); // Changed r2 to result.
return 2 * result;
}).then(result => console.log(result));
Note the 500ms delay in the output, which is what should be expected from the (corrected) setTimeout code.
I posted a full Promises/A+ compliant promise implementation with comments in this answer
How about very simple:
const SimplePromise = function(cb) {
cb(
data =>
(this.data = data) &&
(this.thenCb || []).forEach(chain => (this.data = chain(this.data))),
error =>
(this.error = error) &&
(this.catchCb || []).forEach(chain => (this.error = chain(this.error)))
);
this.then = thenCb =>
(this.thenCb = [...(this.thenCb || []), thenCb]) && this;
this.catch = catchCb =>
(this.catchCb = [...(this.catchCb || []), catchCb]) && this;
};
Example Here: https://codesandbox.io/s/0q1qr8mpxn
Implying that this r2 is actually the result parameter.
The problem with your code is that you do not retrieve the result from the result(2). The first "then" gets executed, prints 2, return 4, but this 4 is just wasted.
I wrote some code with synchronous function only to demonstrate what to do if you want to get this 2,4 output:
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
var res = callback(this.resolver());
var result = new MyPromise(() => { return res; });
return result;
}
}
let promise = new MyPromise(
() => {
return 2;
});
promise
.then(result => {
console.log(result);
return 2 * result;
})
.then(result => {
console.log(result);
});
If you want the resolver to be somewhat async you should use Promises (because return value from function executed inside setTimeout can be retrieved, see
here.
If you are not allowed to use these built-in Promises, you can write them yourself with some dummy deferred object and await them (setInterval) to resolve (should be basically the same logic).
There are a few problems with your original code. Notably you are only executing the constructor argument upon call of the then method and you aren't actually chaining the outputs of the 'then' callbacks.
Here is a very (very!) basic promise implementation based upon adapting your example. It will also work for cases where 'then' is called after the promise is resolved (but not if 'then' is already called - multiple then blocks is not supported).
class MyPromise {
constructor(resolver) {
let thisPromise = this;
let resolveFn = function(value){
thisPromise.value = value;
thisPromise.resolved = true;
if(typeof thisPromise.thenResolve === "function"){
thisPromise.thenResolve();
}
}
if (typeof resolver === "function") {
resolver(resolveFn);
}
}
then(callback) {
let thisPromise = this;
thisPromise.thenFn = callback;
return new MyPromise((resolve) =>{
thisPromise.thenResolve = () => {
thisPromise.value = thisPromise.thenFn(thisPromise.value);
resolve(thisPromise.value);
}
//automatically resolve our intermediary promise if
//the parent promise is already resolved
if(thisPromise.resolved){
thisPromise.thenResolve();
}
});
}
};
//test code
console.log("Waiting for Godot...");
promise = new MyPromise((resolve) =>{
setTimeout(()=>{
resolve(2)
},500);
});
promise.then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result)
});

Extending Promise to support Progress reporting

So I wanted to extend Promise to have a 'progress' part so that i can report progrss with it while using Promise for my Async tasks.
Thus I extended Promise like this:
class promisePro extends Promise {
constructor(fn) {
super(function (resolve, reject) {
fn(resolve, reject, this._progress.bind(this));
});
}
_progress(v) {
if (this.progressCB)
this.progressCB(v);
}
progress(fn) {
this.progressCB = fn;
}
}
and used it:
function ptest() {
return new promisePro((resolve, reject, progress) => {
setTimeout(() => {
progress(0.3)
}, 1000)
setTimeout(() => {
progress(0.6)
}, 2000)
setTimeout(() => {
progress(0.9)
}, 3000)
setTimeout(() => {
resolve(1)
}, 4000)
})
}
and used itt:
ptest().then((r) => {
console.log('finiished: ' + r)
}).progress((p) => {
console.log('progress: ' + p)
})
and got this error:
ptest().then((r) => {
^
TypeError: Promise resolve or reject function is not callable
What am i doing wrong here?
I was using node 7.5, updated to 8.4. Got that error in both versions.
Thanks.
there are a couple of issues to look out for here.
firstly, the "this" keyword is undefined until the super function is called. therefore you could change the constructor to something like below, using a function to reference an instantiated variable to sel
constructor(fn) {
let self;
super(function (resolve, reject) {
fn(resolve, reject, value => self._progress(value));
});
self = this;
}
secondly, keep in mind you are extending the promise type, so when you are calling the "then" function on it, it will return a new promise object, not your new promise type, so the progress function is undefined there. a way to avoid this is to return "this" in the progress function, and in the usage to use the progress function before then, as below
progress(fn) {
this.progressCB = fn;
return this;
}
and for the usage
ptest().progress((p) => {
console.log('progress: ' + p)
}).then((r) => {
console.log('finiished: ' + r)
})
but now you will lose the benefit of the promise and won't be able to chain more promises with progress (still, depends on your use case).
As a suggestion for a different approach, have you tried using Observables?
http://reactivex.io/rxjs/

How to do conditional promise chaining

I'm learning promises/typescript/angular and I want to chain promises conditionally.
This is the actual state of my method:
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean, deferred: ng.IDeferred<T>) {
var promise: ng.IPromise<Object>;
//Step1
if (modeCreation) {
promise = this.$calendrier.Actions.enregistrerEvenementOutlook(edition);
} else {
promise = this.$calendrier.Actions.modifierEvenementOutlook(edition);
}
if (this.$scope.outlook) {
promise.then((x) => {
if (x != '') edition.idOutlook = x.toString();;
return deferred.resolve();
}, (x) => {
return deferred.reject();
});
} else {
//Step2
promise.then((x) => {
if (x != '') edition.idOutlook = x.toString();
return this.$calendrier.Actions.modifierEvenement(edition);
}, (x) => {
//Ajout MessageBox message error
return this.$calendrier.Actions.modifierEvenement(edition);
})
//Step3
.then((x) => {
if (edition.opportunite != null) this.$rootScope.$broadcast("pushEchangeOpportunite", { idOpportunite: parseInt(edition.opportunite), action: 2, IdContact: edition.id, Libelle: edition.title, StartDate: moment(edition.start).toDate() });
return deferred.resolve();
}, (x) => {
return deferred.reject();
});
}
}
I'm familiar of async/await of C#, neither of which gives a problem with conditional chaining, but I'm having trouble achieving the same with promises.
Is it to correct to put a .then not just after the creation of the promise but after an if ?
Is it possible that the .then may never be called because the promise is already finished?
It is fine to chain promises together in any order or use ifs, loops, whatever.
If you call .then on a resolved promise then it will execute instantly, so that's fine too.
The only way the then will not be called is if the promise chain is never resolved or is rejected.
A normal way of chaining might be to return the next object from within the function. This tends to be neater than calling deferred.resolve().
E.g.
var promise = this.$calendrier.Actions.enregistrerEvenementOutlook(edition);
promise = promise.then(function (x) {
return 2 * x;
})
promise = promise.then(function (x) {
return 2 * x;
})
or
var promise =
this.$calendrier.Actions.enregistrerEvenementOutlook(edition)
.then(function (x) {
return 2 * x;
})
.then(function (x) {
return 2 * x;
})
executePromiseModificationEvenement() doesn't need a Deferred. There's absolutely no value in passing one in. Instead, you should be looking to return the promise returned by a promise chain formed within the function. The caller function(s) will need only a minor change.
Straightforwardly, your function can be rewritten with a series of (conditional) promise = promise.then(...) statements with a final return promise. Some of the code repetition can also be addressed.
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean<T>) {
var promise: ng.IPromise<Object>;
promise = modeCreation ?
this.$calendrier.Actions.enregistrerEvenementOutlook(edition) :
this.$calendrier.Actions.modifierEvenementOutlook(edition);
promise = promise.then((x) => {
if (x != '') {
edition.idOutlook = x.toString();
}
});
if (!this.$scope.outlook) {
promise = promise.then(() => {
return this.$calendrier.Actions.modifierEvenement(edition);
}, () => {
return this.$calendrier.Actions.modifierEvenement(edition);
})
.then((x) => {
if (edition.opportunite != null) {
this.$rootScope.$broadcast("pushEchangeOpportunite", {
idOpportunite: parseInt(edition.opportunite),
action: 2,
IdContact: edition.id,
Libelle: edition.title,
StartDate: moment(edition.start).toDate()
});
}
});
}
return promise;
}
However, that may not be the best solution.
It may be more appropriate to perform the if(this.$scope.outlook) test at the point where this.$calendrier.Actions.modifierEvenement()... is called, during chain settlement rather than during the chain building phase. The result will not necessarily be the same because this.$scope.outlook will have had an opportunity to change state.
Personally, I would guess that performing the test later is more appropriate (or inconsequential). If so, the promise chain can be built unconditionally and all the tests performed internally, which if nothing else, is much tidier.
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean<T>) {
return (modeCreation ?
this.$calendrier.Actions.enregistrerEvenementOutlook(edition) :
this.$calendrier.Actions.modifierEvenementOutlook(edition))
.then((x) => {
if (x != '') {
edition.idOutlook = x.toString();
}
})
.catch((x) => { return x; }) // this mid-chain-error-recovery line is rather odd but consistent with the original code. It may be better placed one step earlier.
.then(() => {
if (!this.$scope.outlook) {
return this.$calendrier.Actions.modifierEvenement(edition)
.then(() => {
if (edition.opportunite != null) {
this.$rootScope.$broadcast("pushEchangeOpportunite", {
'idOpportunite': parseInt(edition.opportunite),
'action': 2,
'IdContact': edition.id,
'Libelle': edition.title,
'StartDate': moment(edition.start).toDate()
});
}
});
}
});
}

Categories

Resources