How can i rewrite these JavaScript promises to be less complicated? - javascript

I wrote this, but it looks rough, its for node js so all values need to be gotten in order
import tripfind from '../model/tripfind';
import addbook from '../model/addbook';
import bookfind from '../model/bookfind';
function addBook(info) {
return new Promise((resolve, reject) => {
let rowcount = -1;
bookfind(0, -1, -1).then((result) => {
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
tripfind(info.trip_id, 0).then((xresult) => {
let val = '';
if (xresult === 'false') {
val = 'trip is not valid';
resolve(val);
} else {
addbook(info, rowcount).then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}).catch((err) => {
if (err) {
reject(err);
}
});
}
}).catch((error) => {
reject(error);
});
}).catch((err) => {
if (err) {
reject(err);
}
});
});
}
export default addBook;
thats the code above, the code also gets exported and is handled as a promise function, please help if you can

Without using async / await, this example is functionally equivalent to what you had before:
function addBook (info) {
return bookfind(0, -1, -1).then(result => {
const { rowCount } = result || { rowCount: 0 };
return tripfind(info.trip_id, 0).then(trip =>
trip === 'false'
? 'trip is not valid'
: addbook(info, rowCount)
);
});
}
Sections like
.then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
})
.catch((err) => {
if (err) {
reject(err);
}
});
.catch((error) => {
reject(error);
});
are completely superfluous, so removing them doesn't affect the logic at all. After removing those, and unwrapping your Promise constructor antipattern, you can see the logic becomes a lot more simplified.

You're missing some returns of promises, and if you like this way of writing you should indent then() and catch() in the same way. Also doing this
.catch((err) => {
if (err) {
reject(err);
}
});
is useless, caus you catch an error to reject it. Rewritten in promise pattern (and a bit of optimization):
function addBook(info) {
return bookfind(0, -1, -1)
.then((result) => {
rowcount = result ? result.rowCount : 0
return tripfind(info.trip_id, 0)
.then((xresult) => {
if (xresult === 'false') {
return 'trip is not valid'
}
return addbook(info, rowcount)
})
})
}
Anyway is more clear with the async/await pattern:
async function addBook(info) {
let result = await bookfind(0, -1, -1)
rowcount = result ? result.rowCount : 0
let xresult = await tripfind(info.trip_id, 0)
if (xresult === 'false')
return 'trip is not valid'
let value = await addbook(info, rowcount)
return value
}

like this?
function addBook(info) {
return bookfind(0, -1, -1).then((result) => tripfind(info.trip_id, 0)
.then((xresult) => (xresult === 'false') ?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0)
)
);
}
or with async/await
async function addBook(info) {
const result = await bookfind(0, -1, -1);
const xresult = await tripfind(info.trip_id, 0)
return xresult === "false"?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0);
}
Avoid the Promise/Deferred antipattern: What is the explicit promise construction antipattern and how do I avoid it?
and remove pointless/useless code, like this function for example:
(value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}
which always resolves to value. that's like return value === 1? 1: value
Or all the (error) => { reject(error); } just to "forward" the error to the returned promise.
or this construct
let rowcount = -1;
//....
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
//....
doSomethingWith(rowcount)
which can be summed up as doSomethingWith(result ? result.rowCount : 0)

A lot of people might tell you that async/await will solve this better. I think the problem is more about how the promises are used. If you have access to change the functions you are calling here's what I'd recommend.
Have functions return things of the same shape - e.g., the call to bookfind can return a rejected promise or a resolved promise that wraps undefined or {..., :rowcount:12}. If we remove the undefined result and return a default {..., rowcount:0 } it would simplify the calling functions substantially and contain logic a bit better.
Remove the superfluous function calls (.catchs and the addbook .then bit)
Here's what those functions could look like (not knowing your setup at all)
function tripfind(id, num) {
return new Promise(function(resolve, reject) {
doSomething(function(err, results) {
if (err) return reject(err);
if (results === "false") return reject(new Error("trip is not valid"));
return resolve(results);
});
});
}
function bookfind(id, start, end /*or whatever*/) {
return new Promise(function(resolve, reject) {
findBooks(function(err, results) {
if (err) return reject(err);
// handle odd cases too
if (!results) return resolve({ rowcount: 0 });
return resolve(results);
});
});
}
and the usages
bookfind(0, -1, -1).then(results =>{}).catch(err=>{})
tripfind(id, 0).then(results =>{}).catch(err=>{})
Using Promises this way, we don't have to check the value of results because if it wasn't there, the promise shouldn't resolve. Likewise, the shape of the thing coming out of the promise should always be the same. (I call it a smell when a function returns String|Object|Undefined, it's possible but I'd look hard at it first)
Using this pattern and removing calls that don't do anything (all the .catch calls), we can simplify the code down to:
function addBook(info) {
return bookfind(0, -1, -1).then(({ rowcount }) =>
tripfind(info.trip_id, 0).then(() =>
addbook(info, rowcount)
)
);
}

Related

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

async await with promises is then block required

How to basically async await properly? I have created a helper for AsyncStorage which async awaits automatically but do the users of this also have to use async await or promise approach to get the value?
This code works but unable to use the syntax correctly.
here is my code:
class AsyncStorageHelper {
static getItem = async (key: string) => {
let value: any = "";
try {
value = await AsyncStorage.getItem(key);
} catch (error) {
console.log(`Error item: ${value}`);
throw new Error(`Error ${value}`);
}
return value;
};
}
AsyncStorageHelper.getItem("logins")
.then(result => {
if (result) {
if (result === "1") {
navigate(SCREEN1);
} else {
navigate(SCREEN2);
}
}
})
.catch(err => {
navigate(LOGINSCREEN);
});
How can I convert the AsyncStorageHelper code to async await as depending on the result I want to navigate to different places.
await must be used inside a async function.
async function helper() {
try {
const result = await AsyncStorageHelper.getItem("logins");
if (result) {
if (result === "1") {
navigate(SCREEN1);
} else {
navigate(SCREEN2);
}
}
} catch (error) {
navigate(LOGINSCREEN);
}
}
helper()
Async functions and promise-returning function can be used externally in the same manner.
AsyncStorageHelper.getItem("logins")
.then(result => {
if (result) {
if (result === "1") {
navigate(SCREEN1);
} else {
navigate(SCREEN2);
}
}
})
.catch(err => {
navigate(LOGINSCREEN);
});
Is the same as:
// note: this code must run in another async function
// so we can use the keyword await
try {
const result = await AsyncStorageHelper.getItem("logins");
if (result) {
if (result === "1") {
navigate(SCREEN1);
} else {
navigate(SCREEN2);
}
}
} catch (err) {
navigate(LOGINSCREEN);
}
Note: your code has an unknown code path. What happens when AsyncStorageHelper.getItem("logins") returns a falsy value? You essentially have a noop and this might not be the desired behavior.
class AsyncStorageHelper {
static async getItem(key : string) {
let value: any = ""
try {
value = await AsyncStorage.getItem(key)
} catch (error) {
console.log(`Error item: ${value}`)
throw new Error(`Error ${value}`)
}
return value
}
}
try {
const result = await AsyncStorageHelper.getItem("logins")
if (result)
(result === "1") ? navigate(SCREEN1): navigate(SCREEN2)
} catch(err) {
navigate(LOGINSCREEN)
}

Calling a promise function recursively

I'm trying to call a promise function recursively.
The following call service.getSentenceFragment() returns upto 5 letters from a sentence i.e. 'hello' from 'helloworld. Providing a nextToken value as a parameter to the call returns the next 5 letters in the sequence. i.e. 'world'. The following code returns 'hellohelloworldworld' and does not log to the console.
var sentence = '';
getSentence().then(function (data)) {
console.log(sentence);
});
function getSentence(nextToken) {
return new Promise((resolve, reject) => {
getSentenceFragment(nextToken).then(function(data) {
sentence += data.fragment;
if (data.nextToken != null && data.nextToken != 'undefined') {
getSentence(data.NextToken);
} else {
resolve();
}
}).catch(function (reason) {
reject(reason);
});
});
}
function getSentenceFragment(nextToken) {
return new Promise((resolve, reject) => {
service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
if (data) {
if (data.length !== 0) {
resolve(data);
}
} else {
reject(error);
}
});
});
}
Cause when you do this:
getSentence(data.NextToken);
A new Promise chain is started, and thecurrent chain stays pending forever. So may do:
getSentence(data.NextToken).then(resolve, reject)
... but actually you could beautify the whole thing to:
async function getSentence(){
let sentence = "", token;
do {
const partial = await getSentenceFragment(token);
sentence += partial.fragment;
token = partial.NextToken;
} while(token)
return sentence;
}
And watch out for this trap in getSentenceFragment - if data is truthy but data.length is 0, your code reaches a dead end and the Promise will timeout
// from your original getSentenceFragment...
if (data) {
if (data.length !== 0) {
resolve(data);
}
/* implicit else: dead end */
// else { return undefined }
} else {
reject(error);
}
Instead, combine the two if statements using &&, now our Promise will always resolve or reject
// all fixed!
if (data && data.length > 0)
resolve(data);
else
reject(error);
You could recursively call a promise like so:
getSentence("what is your first token?")
.then(function (data) {
console.log(data);
});
function getSentence(nextToken) {
const recur = (nextToken,total) => //no return because there is no {} block so auto returns
getSentenceFragment(nextToken)
.then(
data => {
if (data.nextToken != null && data.nextToken != 'undefined') {
return recur(data.NextToken,total + data.fragment);
} else {
return total + data.fragment;
}
});//no catch, just let it go to the caller
return recur(nextToken,"");
}
function getSentenceFragment(nextToken) {
return new Promise((resolve, reject) => {
service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
if (data) {
if (data.length !== 0) {
resolve(data);
}
} else {
reject(error);
}
});
});
}

How to stop promise chain after resolve?

I want to stop promise chain after it resolved via some conditions. Below code is might useful to understand what am I saying.
function update(id, data) {
return new Promise((resolve, reject) => {
let conn;
pool.get()
.then((db) => {
conn = db;
if(Object.keys(data).length === 0) {
return resolve({ updated: 0 });
}
else {
return generateHash(data.password);
}
})
.then((hash) => {
conn.query("UPDATE ... ", (err, queryResult) => {
if(err) {
throw err;
}
resolve({ updated: queryResult.affectedRows });
});
})
.catch((err) => { ... })
});
}
Note that pool.get() is promise wrapped API for getting connection pool from MySQL module that I made.
What I'm trying to do is updating user data. And for save server resources, I avoided to update if no data to update(Object.keys(data).length === 0).
When I tried this code, second then(updating db) is always happening even if no data to update!
I read this post, but it didn't worked. Why the promise chain wasn't stopped when I called "return resolve();"? And how to I stop it properly? I really like using Promises, but sometimes, this kind of things make me crazy. It will be very appreciate to help me this problem. Thanks!
P.S. I'm using node v6.2.2 anyway.
Why the promise chain wasn't stopped when I called "return resolve();"?
You've returned from the current then callback and fulfilled the outer promise. But that doesn't "stop" anything, then then chain still will continue by resolving with the return value of the callback.
And how to I stop it properly?
You need to put the then call inside the if to have the condition apply to it:
pool.get()
.then((db) => {
…
if (Object.keys(data).length === 0) {
…({ updated: 0 });
} else {
return generateHash(data.password)
.then((hash) => {
conn.query("UPDATE ... ", (err, queryResult) => {
…
});
})
}
})
.catch((err) => { ... })
And in any case, you should avoid the Promise constructor antipattern! You should only promisify the query method:
function query(conn, cmd) {
return new Promise((resolve, reject) => {
conn.query(cmd, (err, queryResult) => {
if (err) reject(err); // Don't throw!
else resolve(queryResult);
});
});
}
and then use that:
function update(id, data) {
return pool.get()
.then(conn => {
if (Object.keys(data).length === 0) {
conn.close(); // ???
return { updated: 0 };
} else {
return generateHash(data.password)
.then(hash => {
return query(conn, "UPDATE ... ")
}).then(queryResult => {
conn.close(); // ???
return { updated: queryResult.affectedRows };
}, err => {
…
conn.close(); // ???
});
}
});
}
Notice that it might not make sense to get a connection from the pool if you can know beforehand that no query will be made, so probably you should put the if on the top level:
function update(id, data) {
if (Object.keys(data).length === 0) {
return Promise.resolve({ updated: 0 });
} else {
return pool.get()
.then(conn => {
return generateHash(data.password)
.then(hash => {
return query(conn, "UPDATE ... ")
}).then(queryResult => {
conn.close(); // ???
return { updated: queryResult.affectedRows };
}, err => {
…
conn.close(); // ???
});
});
}
}
This would be a good situation to use an if statement:
function update(id, data) {
if (Object.keys(data).length === 0) {
return Promise.resolve({ updated: 0 });
}
let conn;
return pool.get()
.then((db) => {
conn = db;
return generateHash(data.password);
})
.then((hash) => {
return new Promise(function (resolve, reject) {
conn.query("UPDATE ... ", (err, queryResult) => {
if(err) {
reject(err);
}
resolve({ updated: queryResult.affectedRows });
});
});
})
.catch((err) => { ... })
}

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