Rejecting Promise if many error sources - javascript

Im in situation where I have many potential error sources. Is there an elegant solution to this mess?
How should I reject it?
function myFuction(hash) {
return new Promise((resolve, reject) => {
// this could return error
const id = atob(hash);
// this could return error
let data = firstFunction(id);
// return error if not true
if (data && data.id) {
// this could return error
return secondFunction(data.id)
.then(item => {
// return error if not true
if (item) {
// this could return error
return thirdFunction(item)
.then(payload => {
resolve('OK');
});
}
});
}
});
}

Avoid the Promise constructor antipattern! You can use early returns with Promise.reject or just throw errors:
function myFuction(hash) {
return Promise.resolve().then(() => {
// this could throw error
const id = atob(hash);
// this could throw error
let data = firstFunction(id);
// return error if not true
if (!data || !data.id)
return Promise.reject(new Error("…")); // alternative: throw new Error("…");
return secondFunction(data.id);
}).then(item => {
// return error if not true
if (!item)
return Promise.reject(new Error("…")); // same here
return thirdFunction(item);
}).then(payload => 'OK');
}
(Additionally I applied some flattening, but as long as your always return from promise callbacks you could nest as well)

Related

Returning promise from async function

Can promise to be returned like below? Does the catch block return a promise or a normal Error?
async function beforeProcessing(ctx) {
try {
let res = await validateList(ctx)
return new Promise((resolve, reject) => {
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
reject(customErr);
}
else {
resolve();
}
})
} catch (error) {
return new Error(error);
}
}
Yes, but there's no reason to. async functions always return promises. Just handle that logic inline, and throw customErr to reject the function's promise:
async function beforeProcessing(ctx) {
let res;
try {
res = await validateList(ctx)
} catch (error) {
return new Error(error); // *** This is highly suspect
}
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
throw customErr;
}
}
But as I mentioned in a comment above, returning an error object is highly suspect, it fulfills the promise with the error rather than rejecting the promise. Just let the error propagate to the caller (which will reject the async function's promise):
async function beforeProcessing(ctx) {
let res = await validateList(ctx)
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
throw customErr;
}
}
Also, that if condition looks odd vs. the error message. The error message makes it look like the condition should be <= 0, not > 0, but of course I could be inferring incorrectly there.

Promise reject doesn't propagate the error correctly

I receive a complex object, let's say it will look like this:
complexObject: {
fruits: string[],
vegetables: string[],
milk: [] | [{
expirationDate: string,
quantity: string
}]
}
So, the logic is, that when I receive an empty object (milk will be just []), the verifyObject method will return undefined (the further logic is more complex and has to receive undefined...); the same case will be when the method will be called with an empty/undefined object.
The only verification, for the milk, should be like this:
If we have milk, then it needs to have both quantity and expirationDate, else it should return false.
The problem is, that when I send an object like this:
{
'fruits': ['apples', 'bananas'],
'vegetables': ['tomatoes'],
'milk': [{
'quantity': '10'
}]
}
in the checkAndAssign method it will see, the error, print the console.log, but it won't stop there and it will add the object to the result.
Also, in the verifyObject method, it will enter catch block, but upper it won't throw the error and it will resolve the promise instead, returning the result...
I want to stop the execution when I receive a wrong message and propagate the error...
This is the code:
verifyObject(complexObject) {
return new Promise((resolve, reject) => {
const result = {};
const propertiesEnum = ['fruits', 'vegetables', 'milk'];
if (!complexObject) {
resolve({ result: undefined });
} else {
this.checkAndAssign(complexObject, result)
.catch((err) => {
console.log('enter error');
reject({ message: err });
})
if (!Object.keys(result).length) {
console.log('resolve with undefined');
resolve({ result: undefined });
}
console.log('resolve good');
resolve({ result });
}
})
}
private checkAndAssign(complexObject, result) {
return new Promise((resolve, reject) => {
for (const property of complexObject) {
if(complexObject[property] && Object.keys(complexObject[property]).length)
if(property === 'milk' && this.verifyMilk(complexObject[property]) === false) {
console.log('rejected');
reject('incomplete');
}
Object.assign(result, {[property]: complexObject[property]})
console.log('result is...:>', result);
}
console.log('final result:>', result);
resolve(result);
});
}
private verifyMilk(milk:any): boolean {
for(const item of milk) {
if(!(item['expirationDate'] && item['quantity'])) {
return false;
}
}
return true;
}
Calling reject doesn't terminate the function you call it from. reject is just a normal function call. Once the call is complete, the function calling it continues.
If you don't want that, use return:
private checkPropertyAndAssignValue(complexObject, result) {
return new Promise((resolve, reject) => {
for (const property of complexObject) {
if(complexObject[property] && Object.keys(complexObject[property]).length)
if(property === 'milk' && this.verifyMilk(complexObject[property]) === false) {
console.log('rejected');
reject('incomplete');
return; // <================================================ here
}
Object.assign(result, {[property]: complexObject[property]})
console.log('result is...:>', result);
}
console.log('final result:>', result);
resolve(result);
});
}
Then verifyObject needs to wait for the fulfillment of the promise from checkPropertyAndAssignValue. verifyObject's code falls prey to the explicit promise creation anti-pattern: It shoudln't use new Promise, because it already has a promise from checkPropertyAndAssignValue. Avoiding the anti-pattern also helps avoid this error:
verifyObject(complexObject) {
const result = {};
const propertiesEnum = ['fruits', 'vegetables', 'milk'];
if (!complexObject) {
return Promise.resolve({ result: undefined });
}
return this.checkPropertyAndAssignValue(complexObject, result)
.then(() => {
if (!Object.keys(result).length) {
console.log('resolve with undefined');
return { result: undefined };
}
console.log('resolve good');
return { result };
})
.catch((err) => {
console.log('enter error');
throw { message: err }; // *** Will get caught by the promise mechanism and turned into rejection
});
}
As an alternative: If you're writing this code for modern environments, you may find async functions and await to be more familiar, since they use the same constructs (throw, try, catch) that you're used to from synchronous code. For example:
async verifyObject(complexObject) {
const result = {};
const propertiesEnum = ['fruits', 'vegetables', 'milk'];
if (!complexObject) {
return { result: undefined };
}
try {
await this.checkPropertyAndAssignValue(complexObject, result);
// ^^^^^−−−−− waits for the promise to settle
if (!Object.keys(result).length) {
console.log('return undefined');
return { result: undefined };
}
console.log('return good');
return { result };
} catch (err) {
console.log('enter error');
throw { message: err }; // *** FWIW, suggest making all rejections Error instances
// (even when using promises directly)
}
}
private async checkPropertyAndAssignValue(complexObject, result) {
// *** Note: This used to return a promise but didn't seem to have any asynchronous
// code in it. I've made it an `async` function, so if it's using something
// that returns a promise that you haven't shown, include `await` when getting
// its result.
for (const property of complexObject) {
if(complexObject[property] && Object.keys(complexObject[property]).length)
if(property === 'milk' && this.verifyMilk(complexObject[property]) === false) {
throw 'incomplete'; // *** FWIW, suggest making all rejections Error instances
// (even when using promises directly)
}
Object.assign(result, {[property]: complexObject[property]})
console.log('result is...:>', result);
}
console.log('final result:>', result);
return result;
}

TypeScript: Error inside Promise not being caught

I am developing a SPFx WebPart using TypeScript.
I have a function to get a team by name (get() returns also a promise):
public getTeamChannelByName(teamId: string, channelName: string) {
return new Promise<MicrosoftGraph.Channel>(async (resolve, reject) => {
this.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient) =>{
client
.api(`/teams/${teamId}/channels`)
.filter(`displayName eq '${channelName}'`)
.version("beta")
.get((error, response: any) => {
if ( response.value.length == 1) {
const channels: MicrosoftGraph.Channel[] = response.value;
resolve(channels[0]);
} else if (response.value.length < 1) {
reject(new Error("No team found with the configured name"));
} else {
reject(new Error("Error XY"));
}
});
})
.catch(error => {
console.log(error);
reject(error);
});
});
}
I call this function like this:
public getConversations(teamName: string, channelName: string, messageLimitTopics: number = 0, messageLimitResponses: number = 0) {
return new Promise<any>(async (resolve, reject) => {
try {
this.getTeamGroupByName(teamName)
.then((teamGroup: MicrosoftGraph.Group) => {
const teamId: string = teamGroup.id;
this.getTeamChannelByName(teamId, channelName)
.then((teamChannel: MicrosoftGraph.Channel) => {
const channelId: string = teamChannel.id;
this.getChannelTopicMessages(teamId, channelId, messageLimitTopics)
.then((messages: MicrosoftGraph.Message[]) => {
const numberOfMessages = messages.length;
... // omitted
});
});
});
} catch(error) {
reject(error);
}
});
}
And this getConversations() function itself is called from my webpart code:
public getConversations() {
if (this.props.teamName && this.props.teamName.length > 0 &&
this.props.channelName && this.props.channelName.length > 0) {
GraphService.getConversations(this.props.teamName, this.props.channelName, this.props.messageLimitTopics, this.props.messageLimitResponses)
.then((conversations) => {
.. // omitted
})
.catch((err: Error) => {
console.log(err);
this.setState({errorMessage: err.message});
});
} else {
// Mandatory settings are missing
this.setState({errorMessage: strings.MandatorySettingsMissing});
}
}
So, as you can see, above, I want to write out the error (message) I receive from the reject inside the getConversations() functions. The problem is, that I don't receive this rejection with the error, but in the console I see the following:
Uncaught Error: No team found with the configured name
I added the .catch() blocks you see above inside getTeamChannelByName() but this doesn't get hit during debugging.
Haven't worked much with promises and I am still confused a bit about them, so I guess that I probably have constructed the promise chain wrongly, maybe placed the catch block(s) in the wrong position(s)?
I had a look at common mistakes done with Promises and I certainly had what is called a "Promise Hell".
So, I am not 100% sure yet, but I guess, the result of this was that there was no catch block for my .then("Promise") so the rejection went to nowhere.
I have now changed the calling method to this, and I also removed the try/catch blocks, as it is unnecessary:
public getConversations(teamName: string, channelName: string, messageLimitTopics: number = 0, messageLimitResponses: number = 0) {
return new Promise<any>(async (resolve, reject) => {
let teamId: string = null;
let channelId: string = null;
this.getTeamGroupIdByName(teamName)
.then(tId => {
teamId = tId;
return this.getTeamChannelIdByName(teamId,channelName);
})
.then(cId => {
channelId = cId;
return this.getChannelTopicMessages(teamId, channelId, messageLimitTopics);
})
.catch(error => {
reject(error);
});
});
}

how to use js Promise to eliminate pyramid of doom

i'm trying to understand how to use js Promise api to refractor a code that has lots of nested IF.
example when getting JSON object from localstorage a normal code would look like
function $storage(key,default) {
let json = localStorage.getItem(key);
if(json === null) return default;
try{ // <-- need try catch in case value was not valid json object
json = JSON.parse(json);
} catch (e) {
json = default;
}
return typeof json === 'object' ? json : default;
}
the readibility of this code is not that good. so i thought may be i can utilize js Promise to rewrite it into
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : HOW_TO_THROW_ERROR() )
//on more validation step if needed
.then( json => typeof json === 'object' ? json : HOW_TO_THROW_ERROR() )
.then( valid_json => { return = valid_json } )
.catch( error => { ret = default; console.warn('json invalid',e); } );
return ret;
}
now i want to know how can i throw an exception inside then so that the catch can caught it and execute default ?
is this valid usage of js promise of am i wasting performance
You could use Promise.reject() to throw an error:
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : Promise.reject("invalid json") )
.then( valid_json => { return = valid_json } )
.catch( err => { ret = default; console.warn(err.message); } );
return ret;
}
Although I find the following more legible and idiomatic.
function $storage(key,default) {
let json = localStorage.getItem(key);
if(json === null || typeof json !== 'object') json = default;
try{
json = JSON.parse(json);
} catch (e) {
json = default;
} finally {
return json
}
}
Promises are used, as you surely know, for asynchronous computation. Any other use might confuse other programmers.
You can use thrown to thrown the errors and then handle them in catch method
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
throw 'oh, no!';
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
// The following behaves the same as above
p1.then(function(value) {
console.log(value); // "Success!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
But if thrown some errors in async functions the catch is never called.
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // This is never called
});
source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
in JavaScript you can use the keyword throw to throw any error.
Examples from MDN:
throw "Error2"; // generates an exception with a string value
throw 42; // generates an exception with the value 42
throw true; // generates an exception with the value true
throw new Error("Error");
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : throw new Error("invalid json") )
//on more validation step if needed
.then( json => typeof json === 'object' ? json : throw new Error("invalid json") )
.then( valid_json => { return = valid_json } )
.catch( err => { ret = default; console.warn(err.message); } );
return ret;
}
You could basically just do the following, because if the parse fails, it will be catched automatically.
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then(ls => JSON.parse(ls.getItem(key)) )
.then(valid_json => { return = valid_json } )
.catch(err => { ret = default; console.warn(err.message); } );
return ret;
}
The problem I see is just about JSON.parse, wrapping it in a more usable function you get something like:
function safeParse(x) {
try {
return JSON.parse(x);
} catch(e) {
// Log the problem
return null;
}
}
function parmval(key, defval) {
var json = safeParse(localStorage.get(key));
return (typeof json === "object") ? json : defval;
}
Promises are about asynchronous operations, not IFs.

Using throw in promises

I would like to create a function that returns a promise and if something throws an error within, it returns promise reject.
function promiseFunc(options) {
return new Promise(() => {
return options;
});
}
function myfunc(options) {
return new Promise(() => {
if (!options) throw new Error("missing options");
return promiseFunc(options).then((result) => {
if (result.throwerr) throw new Error("thrown on purpose");
return result.value;
});
});
};
My test as follows:
const myfunc = require("./myfunc");
describe('myfunc', () => {
it('should fail without options', () => {
return myfunc()
.then((result) => { throw new Error(result) }, (err) => {
console.log("test #1 result:", err.message === "missing options");
});
});
it('should fail on options.throwerr', () => {
return myfunc({throwerr: true})
.then((result) => {}, (err) => {
console.log("test #2 result:", err.message === "thrown on purpose");
});
});
it('should return options.value', () => {
return myfunc({value: "some result", throwerr: false})
.then((result) => {
console.log("test #3 result:", result === "some result");
}, (err) => {});
});
});
The first test pass, but the second and third fails.
Log #2 does not even run, so I assumed the "throw on purpose" messes up something, therefore I created test #3, where I don't throw anything, but it still fails.
What am I missing?
Solution:
function promiseFunc(options) {
return new Promise(resolve => {
return resolve(options);
});
}
function myfunc(options) {
return new Promise((resolve, reject) => {
if (!options) throw new Error("missing options");
return promiseFunc(options).then(result => {
if (result.throwerr) throw new Error("thrown on purpose");
return resolve(result.value);
}).catch(err => {
return reject(err);
});
});
};
You forgot to pass a function with resolve and reject parameters, so your promises just don't work.
function promiseFunc(options) {
return new Promise(resolve => { // resolve function
resolve(options)
})
}
module.exports = function myfunc(options) {
return new Promise((resolve, reject) => { // since you may either resolve your promise or reject it, you need two params
if (!options) {
return reject(new Error("missing options"))
}
return promiseFunc(options).then(result => {
if (result.throwerr) {
return reject(new Error("thrown on purpose"))
}
resolve(result.value)
})
})
}
... and the test (mocha)
const assert = require('assert'),
myfunc = require("./myfunc")
describe('myfunc', () => {
it('should fail without options', done => { // mind the callback, promises are always async
myfunc()
.catch(err => {
assert(err.message === "missing options")
done() // <- called here
})
})
it('should fail on options.throwerr', done => {
myfunc({throwerr: true})
.catch(err => {
assert(err.message === "thrown on purpose")
done()
})
})
it('should return options.value', done => {
return myfunc({value: "some result", throwerr: false})
.then(result => {
assert(result === "some result")
done()
})
})
})
I would like to create a function that returns a promise and if something throws an error within, it returns promise reject.
This will do it ...
var q = require('q'); // In recent versions of node q is available by default and this line is not required
function iReturnAPromise(num) {
var def = q.defer();
if (typeof num=== 'number') {
try {
var value = 100 / num;
def.resolve(value);
} catch(e) {
def.reject("oops a division error - maybe you divided by zero");
}
} else {
def.reject("o no its not a number");
}
return def.promise;
}
PS this function was coded freehand and has not been tested - but this will work. Obviously try catch should be used sparingly.
PS I prefer the q library implementation of promise instead of the default node promise library - they take a very different approach. q dispenses with all the wrapping!
using the promise library u wanted ...
function iReturnAPromise(num) {
return new Promise(function(resolve, reject) {
if (typeof num === 'number') {
try {
var value = 100 / num;
resolve(value);
} catch (e) {
reject("oops a division error - maybe you divided by zero");
}
} else {
reject("o no its not a number");
}
})
}
iReturnAPromise(7).then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
// Unexpectedly this is not an error in node 5.6 because div by 0 is not an error operation anymore!
iReturnAPromise(0).then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
iReturnAPromise("fred").then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
you can see why i prefer the q syntax :)

Categories

Resources