I'm looking to write some code that chains together some promises. I have a condition that based on the result of one promise I either call the next function that returns a promise and continue chaining another few functions, or I do nothing (effectively end the promise chain).
I have the following three possible solutions, I kinda think they all are a bit messy though.
Here is my first approach, what I dislike here is the nested promises.
initalAsyncCall()
.then((shouldContinue) => {
if (shouldContinue) {
return nextStep()
.then(() => anotherStep())
}
})
.catch((error) => {
handleError(error);
})
Here is my second. This one seems a bit longer and possibly harder to read
const shouldContinuePromise = initialAsyncCall();
const nextStepPromise = shouldContinuePromise.then((shouldContinue) => {
if (shouldContinue) return nextStep();
});
Promise.all([shouldContinuePromise, nextStepPromise])
.spread((shouldContinue) => {
if (shouldContinue) return anotherStep();
})
.catch((error) => {
handleError(error);
});
And finally here is my last approach. What I don't like here is I am throwing an error when it's not really an error.
initalAsyncCall()
.then((shouldContinue) => {
if (!shouldContinue) throw new HaltException()
return nextStep();
})
.then(() => anotherStep())
.catch(HaltException, (ex) => {
// do nothing... maybe some logging
})
.catch((error) => {
handleError(error);
})
Your first approach seems fine, to avoid the nesting you can return the promise and add an extra then block for the nested part like this
initalAsyncCall()
.then((shouldContinue) => {
if (shouldContinue) {
return nextStep()
} else {
throw Error('skip next step')
}
})
.then(() => anotherStep())
.catch((error) => {
handleError(error);
})
If you don't like throwing an unnecessary error in the third approach, You could use async/await to have more control and get rid of the function scope/nesting problem, which is also recommended for the new nodejs versions due to better error stack traces.
try {
const shouldContinue = await initalAsyncCall()
if (shouldContinue) {
await nextStep()
await anotherStep()
// or await Promise.all([nextStep(), anotherStep()]) if they're not dependent
}
}
catch (error) {
handleError(error);
}
Related
I am struggling with some code... The 2 examples below I would think would work the same but the second example throws an error? I am also struggling to figure out the error, it's not bubbling up? Admittedly I am not a seasoned node developer so any guidance would be much appreciated! If it's relevant the create method in the module is calling the sequelize create.
This works
var p1 = deliverabiltyConfigs.create2(cfgObject);
return Promise.all([p1]).then(function([res1]) {
res.json({result: res1})
});
This does not
deliverabiltyConfigs.create2(cfgObject).then(res1 =>{
res.json({result: res1})
})
Here is the function that I am calling in a controller module
exports.create2 = (dConfig) => {
DeliverabilityConfig.create(dConfig)
.then(data => {
return data
})
.catch(err => {
return {
message:
err.message || "Some error occurred while createing this config."
};
});
};
The create2 function always returns null, so neither invocation will work. Promise.all([p1]) hides the problem, returning a promise to perform an array of no promises.
create2(cfgObject).then(res1 =>{ attempts to invoke then() on null, generating a more obvious error. But neither way works.
Fix by deciding which promise syntax you want, using each as follows:
Using original promise syntax....
exports.create2 = dConfig => {
// note the return
return DeliverabilityConfig.create(dConfig)
.catch(err => {
const message = err.message || "Some error occurred while createing this config.";
return { message };
});
};
// caller
deliverabiltyConfigs.create2(cfgObject).then(result =>{
res.json(result);
})
With recent syntactic sugar...
exports.create2 = async (dConfig) => {
try {
// its fine to not await here, since the caller will await
// but just to illustrate how you might perform more async work here...
return await DeliverabilityConfig.create(dConfig);
} catch (err) {
const message = err.message || "Some error occurred while createing this config."
return { message }
}
}
// caller
var result = await deliverabiltyConfigs.create2(cfgObject);
res.json(result);
Use Promise.all() to run >1 promise concurrently. You've only got one promise in the OP, so no reason for it here.
module.exports.getTopMeal = function () {
mealKitModel.find({ "top_meal": true })
.then((kits) => {
return kits;
})
.catch((err) => {
console.log("Error finding top mealkit");
})
}
Here, I am trying to find objects which have the top_meal attribute true from the mongoDB. When I put "console.log(kits)" statement in.then(kits) part, it gives me desired output. But when I import this function in another javascript file, the return value of this function shows undefine. I am returning kits in .then() part so that first execution of find promise completes then it returns kits variable.
Please, anyone, show me where am I wrong?
You need to return the promise.
module.exports.getTopMeal = function () {
return mealKitModel.find({ "top_meal": true })
.then((kits) => {
return kits;
})
.catch((err) => {
console.log("Error finding top mealkit");
})
}
Now you can do
getTopMeal().then(topMeal => {
console.log(topMeal)
})
In addition. Since the find method return kits, you don't need to explicitely return it, so the function becomes:
module.exports.getTopMeal = function () {
return mealKitModel.find({ "top_meal": true })
}
Note that I also removed the catch function from mealKitModel.find since generally you don't want to catch every function and log it out, but instead have a single error handler at the end. It's better practice just to return the promise and handle it later.
Basically: If you catch in mealKitModel.find you can't catch the error from getTopMeal.
But now, with the refactor where you simply return the promise you can now do:
getTopMeal().then(topMeal => {
console.log(topMeal)
})
.catch(e => {
console.log('now we can catch the error')
console.log(e)
})
I am making a test app using systeminformation. I'm trying to make it so that each then waits for the previous function to finish. The problem I'm having is that the functions I am running inside are also promises, so the next then runs before the function finishes.
const si = require('systeminformation');
var cpuObj;
function initCPU() {
return new Promise(resolve => {
si.cpu()
.then(data => cpuObj = data)
.catch(err => console.log(err))
.then(() => {
setTimeout(() => console.log("timer"), 3000);
})
.then(() => {
si.cpuTemperature().then(data => console.log(data));
})
.then(() => {
console.log("here");
});
});
}
function test() {
console.log(cpuObj);
}
initCPU().then(() => {
test();
});
Output:
here
{ main: -1, cores: [], max: -1 }
timer
Expected Output:
{ main: -1, cores: [], max: -1 }
timer
here
A few points that need to be addressed:
setTimeout() does not return a promise, so you need to promisify and return it.
Flatten your chain by returning the promises from within each of the continuations rather than attempting to chain continuations within other continuations (i.e. then() inside of then()).
Do not wrap the continuation chain with a promise constructor, as the chain itself is already a promise, just return it directly instead. This is considered an antipattern.
Do not use globals, because it makes the initCPU() no longer re-entrant safe. Multiple calls to initCPU() before the promise returned by the first call resolves will result in unexpected behavior otherwise. Instead, use the appropriate scope to pass values along, which in this case is the function itself.
Allow errors to propagate to the caller and let the caller decide how to handle the error. Do not handle errors from within initCPU() unless you expect to use a fallback and continue to provide meaningful data to the caller.
const si = require('systeminformation');
const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });
function initCPU() {
// use local scope, not global
let cpuObj;
// return this promise chain directly
return si.cpu()
.then(data => {
cpuObj = data;
// return the promise to the chain
return delay(3000);
})
// let caller handle errors
// .catch(err => console.log(err))
// flatten your chain
.then(() => {
console.log('timer');
// return the promise to the chain
return si.cpuTemperature();
})
// flatten your chain
.then(data => {
console.log(data);
console.log('here');
// pass data to caller
return cpuObj;
});
}
function test(cpuObj) {
// received from last continuation of initCPU()
console.log(cpuObj);
}
initCPU()
.then(test)
// handle error from caller
.catch(err => {
console.log(err);
});
If you just want to query the cpu object immediately, and query cpuTemperature after 3 seconds, I'd do something like this using Promise.all():
// default to 3 seconds, allow it to be configurable
function initCPU(ms = 3000) {
return Promise.all([
si.cpu(),
delay(ms).then(() => si.cpuTemperature())
]).then(([cpu, cpuTemperature]) => ({
cpu,
cpuTemperature
}));
}
function test (obj) {
console.log(obj.cpu);
console.log(obj.cpuTemperature);
}
initCPU()
.then(test)
.catch(err => {
console.log(err);
});
I was reading some nodejs tutorial which talks about rejection in nodejs. They say that it's best practice to reject an error instead of a string or an plain text. Taking example of this code.
This is a example of rejecting a string
function cookMeat(chef){
grillMeat(chef)
.then(meat => {
if(chef.isTired){
return Promise.reject(chef.tiredReason);
}
return Promise.resolve(meat);
})
}
function cookNoodle(cheif){
boilNoodle(chef)
.then(noodle => {
if(chef.isTired){
return Promise.reject(chef.tiredReason);
}
return Promise.resolve(noodle);
})
}
function cook(){
let chef
prepareFood()
.then(c => {
chef = c;
return true;
})
.then(() => cookMeat(chef))
.then(() => cookNoodle(chef))
.catch(err => {
state: Fail,
reason: error
})
.then(res => {
state:Ready
})
}
cook()
.then((res) => serveCustomer(res))
And this is a example of rejecting an error
function cookMeat(chef){
grillMeat(chef)
.then(meat => {
if(chef.isTired){
return Promise.reject(new Error(chef.tiredReason));
}
return Promise.resolve(meat);
})
}
function cookNoodle(cheif){
boilNoodle(chef)
.then(noodle => {
if(chef.isTired){
return Promise.reject(new Error(chef.tiredReason));
}
return Promise.resolve(noodle);
})
}
function cook(){
let chef
prepareFood()
.then(c => {
chef = c;
return true;
})
.then(() => cookMeat(chef))
.then(() => cookNoodle(chef))
.catch(err => {
state: Fail,
reason: error.message
})
.then(res => {
state:Ready
})
}
cook()
.then((res) => serveCustomer(res))
Since I want to use reject to skip part of the promise chain. So I am wondering if there are any difference?
wPromise rejections are similar to throwing exceptions / error objects.
There are two rules that apply to throwing exceptions that apply here too:
In javascript, it's better to throw an Error object. Among other things, you will get stack information. It's also what most people expect when using a javascript code base.
Don't use exceptions for flow-control
The second one is such a common advice, you can google it verbatim and learn more. You're using Promise rejections as flow control and this is a bad idea.
Your functions can be rewritten a bit though. This is even better:
function cookMeat(){
grillMeat()
.then(meat => {
if(meat.isRaw){
throw new Error(meat.rawReason);
}
return meat;
});
}
function cookNoodle(){
boilNoodle()
.then(noodle => {
if(noodle.isRaw){
throw new Error(noodle.rawReason);
}
return noodle;
})
}
function cook(){
return prepareFood()
.then(() => cookMeat())
.then(() => cookNoodle())
.catch(err => {
state: Fail,
reason: error.message
})
.then(res => {
state:Ready
})
}
cook()
.then((res) => talkWithCustomer(res))
I got rid of your Promise.reject and Promise.resolve statements, because they are unneccary from within a then() function. The advice to use them only really applies 'outside' of then() chains.
I'm implementing my service using Node.js with AWS DynamoDB (aws-sdk).
It's unclear for me how to implement the following scenario with promises:
I get a request to modify an entity with specified attributes
I'm trying to find the entity in the DB (async call find)
If the entity not found then create one with initial state (async call createInitialStateObject)
Modify the entity (which was in the DB before or just created on step 3) according to specific rules (async call applyModifications)
This is my first attempt:
function scenario(params) {
find(params).then((data) => {
let objectExists = checkExistense(data);
if (!objectExists) {
createInitialStateObject(params).then((data) => {
console.log("Object created");
// OK
}).catch((err) => {
console.error("Object not created");
// exit and return error
});
}
applyModifications(params).then((data) => {
// OK, return data
}).catch((err) => {
// exit and return error
});
}).catch((err) => {
// exit and return error
});
}
But the flaw here is that creation could happen before the modification, it's not bound to happen one after another.
The other attempt works, but looks a bit weird. I create an empty promise to call in case the object already exists:
function scenario(params) {
find(params).then((data) => {
let conditionalPromise = new Promise((resolve) => {
resolve(null);
});
let objectExists = checkExistense(data);
if (!objectExists) {
conditionalPromise = createInitialStateObject(params);
}
conditionalPromise.then((data) => {
applyModifications(params).then((data) => {
// OK, return data
}).catch((err) => {
// exit and return error
});
}).catch((err) => {
// exit and return error
});
}).catch((err) => {
// exit and return error
});
}
How it should be implemented in a right way?
Creating 'empty' or sync. Promises isn't unusual. There is even a short way of doing that: Promise.resolve(value) creates and resolves a Promise immediately.
Besides that you should make use of proper chaining and stop nesting things so much. Once you are in a chain, you don't even need to resolve an empty promise as a return value of a non thenable object is interpreted as exactly this.
function scenario(params) {
return find(params)
.then(data => {
let objectExists = checkExistense(data);
if (!objectExists) {
return createInitialStateObject(params);
}
// if we return nothing (or null in your case) this will be the same as return Promise.resolve()
return null;
})
.then(data => applyModifications(params))
.then(data => console.log(data))
.catch(err => console.log(err));
// exit and return error
}