Improved way to deal with callbacks inside promises - javascript

I have the following code that uses callbacks inside promises:
const clue = 'someValue';
const myFunction = (someParam, callback) => {
someAsyncOperation(someParam) // this function returns an array
.then((array) => {
if (array.includes(clue)){
callback(null, array); // Callback with 'Success'
}
else{
callback(`The array does not includes: ${clue}`); // Callback with Error
}
})
.catch((err) => {
// handle error
callback(`Some error inside the promise chain: ${err}`) // Callback with Error
})
}
and call it like this:
myFunction (someParam, (error, response) => {
if(error) {
console.log(error);
}
else {
// do something with the 'response'
}
})
Reading some documentation, I found that there is some improved way to do this:
const myFunction = (someParam, callback) => {
someAsyncOperation(someParam) // this function returns an array
.then((array) => {
if (array.includes(clue)){
callback(array);
}
else{
callback(`The array does not includes: ${clue}`);
}
}, (e) => {
callback(`Some error happened inside the promise chain: ${e}`);
})
.catch((err) => {
// handle error
callback(`Some error happened with callbacks: ${err}`)
})
}
My question:
In the sense of performance or best practices, it's okay to call the 'callback' function inside the promise as the two ways show or I'm doing something wrong, I mean some promise anti-pattern way ?

This seems really backwards and takes away from the benefits of promises managing errors and passing them down the chain
Return the asynchronous promise from the function and don't interrupt it with callbacks. Then add a catch at the end of the chain
const myFunction = (someParam) => {
// return the promise
return someAsyncOperation(someParam) // this function returns an array
.then((array) => {
return array.includes(clue) ? array : [];
});
}
myFunction(someParam).then(res=>{
if(res.length){
// do something with array
}else{
// no results
}
}).catch(err=>console.log('Something went wrong in chain above this'))

Do not use callbacks from inside of promises, that is an anti-pattern. Once you already have promises, just use them. Don't "unpromisify" to turn them into callbacks - that's moving backwards in code structure. Instead, just return the promise and you can then use .then() handlers to set what you want the resolved value to be or throw an error to set what you want the rejected reason to be:
const clue = 'someValue';
const myFunction = (someParam) => {
return someAsyncOperation(someParam).then(array => {
if (!array.includes(clue)){
// reject promise
throw new Error(`The array does not include: ${clue}`);
}
return array;
});
}
Then, the caller would just do this:
myFunction(someData).then(array => {
// success
console.log(array);
}).catch(err => {
// handle error here which could be either your custom error
// or an error from someAsyncOperation()
console.log(err);
});
This gives you the advantage that the caller can use all the power of promises to synchronize this async operation with any others, to easily propagate errors all to one error handler, to use await with it, etc...

Related

Method does not wait for Promise to be resolved

I have a function that I'm trying to call and basically force it to wait for a response before continuing onto the next thing.
I have two functions, both are asynchronous.
The first one looks something like this, with all parameters that begin with an '_' to be used as callbacks:
async function formatJson(input, _sendToThirdParty, _handleLogs, _setDimensions)
{
...do some work here to format the payload
if(onlineConnectionRequired)
{
_setDimensions(itemToUpdate, object);
}
else {
// Do non-online based transformations here
}
...do more work after the above
}
Basically from that, I'm trying to call this method setDimensions which looks like this:
async function setDimensions(itemToUpdate, object) {
try
{
if(itemToUpdate != null)
{
console.log("Loading dimensions");
await Promise.resolve(function() {
ns.get(`inventoryItem/${object['Item ID']}?expandSubResources=true`)
.then((res) => {
console.log("Inventory Item Loaded. Updating dimensions...");
itemToUpdate.consignments.push(
{
consignmentID: object.conID,
barcode: object.barcode,
itemID: '', // leaving as empty for now
width : res.data.custitem_width,
length : res.data.custitem_length,
height : res.data.custitem_height,
weight : res.data.custitem_weight,
fragile: object.fragile === 'T' ? 1 : 0,
description: object.description
}
);
console.log("Dimensions Finalised");
})
});
}
}
catch(err)
{
console.log(err);
const message = `Error attempting to set the dimensions for ${object['Item ID']}`;
console.log(message);
throw new Error(message);
}
}
The problems I'm coming across are:
The code from the first method continues on before waiting for the promise to resolve, but I need it to wait so I can fully finish building up the payload before it continues on doing the next bits
If I try and include the await keyword before my call to _setDimensions(...) in the first method, I get an error "SyntaxError: await is only valid in async function", but I would've thought that it was an async function?
If anyone could help, that would be incredibly appreciated! Thank you!!
The correct design of your functions are below:
formatJson(input, (err, value) => {
if(err) {
// Error handler goes here
//console.log(err);
throw err;
} else {
// Implementation for working with returned value
console.log(value);
}
});
function formatJson(input, callback)
{
//...do some work here to format the payload
if(onlineConnectionRequired)
{
setDimensions(itemToUpdate, object)
.then((updatedItem) => {
// Implement anything here to work with your
// result coming from setDimensions() function
//console.log(updatedItem);
// Callback with null error and updatedItem as value
callback(null, updatedItem);
})
.catch((err) => {
// Callback with err object and null value
callback(err, null);
});
}
else {
// Do non-online based transformations here
}
//...do more work after the above
}
function setDimensions(itemToUpdate, object) {
try
{
if(inventoryItemID != null)
{
console.log("Loading dimensions");
return new Promise(function(resolve, reject) {
ns.get(`inventoryItem/${object['Item ID']}?expandSubResources=true`)
.then((res) => {
console.log("Inventory Item Loaded. Updating dimensions...");
itemToUpdate.consignments.push(
{
consignmentID: object.conID,
barcode: object.barcode,
itemID: '', // leaving as empty for now
width : res.data.custitem_width,
length : res.data.custitem_length,
height : res.data.custitem_height,
weight : res.data.custitem_weight,
fragile: object.fragile === 'T' ? 1 : 0,
description: object.description
}
);
console.log("Dimensions Finalised");
resolve(itemToUpdate);
})
.catch((err) => reject(err));
});
}
}
catch(err)
{
console.log(err);
const message = `Error attempting to set the dimensions for ${object['Item ID']}`;
console.log(message);
throw new Error(message);
}
}
Mistakes in your code:
Your formatJson function had async keyword but your formatJson function had callback functions named _sendToThirdParty, _handleLogs, _setDimensions. There are 3 types of implementation to create asynchronous codes. You can use callbacks, Promises or async/await. But Promises and async/await are the same except their uses cases and syntaxses. When you define a function as async fn() {...} it basically return a new Promise, so it is equal saying fn() { return new Promise(); }. Functions with callbacks have a shape like function(params, callback) { callback(cbParams); } you can use call callback function in a several branches in your function. But you only have single callback function, your code had 3 callback functions. Also note that functions with callback don't have async keyword. This is not valid because as I mentioned earlier, an async function will return a Promise. So you should not (but you can) define a function as async function(params, callback) like you did in your first method. This is definition is not wrong and it can work but it's not valid.
Your second method was an async function which was returning nothing. So I have changed it to normal function with returning a Promise.
Is the formatJson method beeing called inside an async method? It need to be, and before _setDimensions you need to add a await keyword.
And, as Daniel said, use the promise constructor.

JS - how to hook into success and error callbacks from a different file

I have my JS set up as follows.
App1.js
function x(){
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
// This is a callback function, invoked when performAsyncTask is done.
// I can handle resultObject and errorObject here.
});
}
Let's say I have other files as part of this app. Each of them calls x(), and would invoke their own version of a handleSuccess() or handleError() function depending on the results of the call to x().
How can I structure the call to x() such that I can achieve this? It's almost like I want to "listen" to the results of performAsyncTask() from App1.js, but I'm not sure how to do that.
If you need to stay with callbacks (due to very old JS clients or whatever), you can provide the callback as parameter to x:
function x(myCallback) {
thirdPartyLibrary.performAsyncTask(myCallback);
}
// other file:
x((resultObject, errorObject) => {
// handle just like before
});
You could even change it to two callbacks, depending on the result. Only one of your callbacks will be called in the end:
function x(successCallback, errorCallback) {
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
if (errorObject) return errorCallback(errorObject);
else return successCallback(resultObject);
});
}
// other file:
x(
function handleSuccess(resultObject) {
// handle success
},
function handleError(errorObject) {
// handle error
}
);
Have x return a Promise that resolves with resultObject if there's no error, or rejects with errorObject if there is an error. Then, callers of x can chain .then onto the Promise to handle successes, and chain .catches to handle failures:
function x(){
return new Promise((resolve, reject) => {
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
if (errorObject) reject(errorObject);
else resolve(resultObject);
});
});
}
x()
.then(result => {
// handle successful result
})
.catch(err => {
// handle error
});

How to iterate an array in javascript, with promises inside the loop block and wait for all promises to be completed to continue

I need to iterate an array in javascript with some values that will be used to call an asynchronous function that returns a promise. I can´t continue the next code section without all promises were completed.
In the following example, the function "processInvoices" has to resolve a promise until all promises inside were completed (assume that "confirmInvoice" is an asynchronous function that has different response times):
processInvoices(invoices)
{
return new promise((resolve,reject)=>
{
invoices.forEach(number=>
{
confirmInvoice(number)
.then(result=>{
if (!result)
{reject('Error confirming the invoice');}
});
});
resolve(true); // Resolving here doesn´t mean that all promises were completed!
});
}
init() // triggered at load..
{
let invoices = [2,4,8,16,31];
processInvoices(invoices)
.then(result=>
{
if (result) // It´s probable that the following message isn´t accurate:
console.log('All invoices were processed');
}).catch(reason=>{console.error(reason)});
}
With the code above, I can´t be sure that the "console.log (or any routine)" will be executed right away after all promises were completed.
UPDATE Promise.all(iterable) solves the problem :
processInvoices(invoices)
{
return new promise((resolve,reject)=>
{
var promisesArray = []; // store all promises here
invoices.forEach(number=>
{
promisesArray.push(confirmInvoice(number));
});
Promise.all(promisesArray).then (results=>{
// validate all results and reject if necessary...
if (validateResults(results)) {
// if all ok
resolve('All invoices were processed successfully');
}
else {
reject('Error processing one or more invoices'); // just for demo purposes
}
});
});
}
init() // triggered at load..
{
let invoices = [2,4,8,16,31];
processInvoices(invoices)
.then(result=>
{
console.log(result);
}).catch(reason=>{console.error(reason)});
}
forEach runs synchronously. If you want to wait for all Promises to resolve before the full processInvoices resolves, you should use Promise.all instead; map each invoice number to a Promise and call Promise.all on the resulting array of Promises. Also, your
if (!result) {resolve(false);}
sounds like it's an attempt to handle an error, in case there is no result - in this case, you should reject the Promise instead of calling resolve. Ideally, the failed confirmInvoice call would result in a rejected Promise, but if that's not something you can fix, throw an error if result is falsey so you can handle it in a catch in init. For example:
function processInvoices(invoices) {
return Promise.all(
invoices.map(number => (
confirmInvoice(number)
.then(result => {
// This will result in the `Promise.all` rejecting:
if (!result) throw new Error('Failed to confirm invoice ' + number);
})
))
);
}
function init() {
const invoices = [2, 4, 8, 16, 31];
processInvoices(invoices)
.then(result => {
console.log('All invoices were processed');
})
.catch((err) => {
console.log('err: ' + err);
});
}

Optional catch in javascript promises

The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined
Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}
I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}
You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))
Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));

Start javascript promise bluebird chain in way that handles errors

The motivation for this is to be able to catch all possible errors with the ending .catch, even ones that happen in initial synchronous code.
I want to start my promise chain like so:
const bbPromise = require('bluebird');
bbPromise.do(() => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync();
})
.then((result) => {
// Do something with result
})
.catch((err) => {
console.log('err: ', err);
});
Is there a function that works like bbPromise.do function? bbPromise.resolve just gives me the whole lambda function that was passed in, unexecuted.
I know I could do something like this:
bbPromise.bind({}).then(() => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync();
})
...
or even:
new bbPromise((resolve, reject) => {
someTask(); // Could throw
return someVar.doSomeOtherTaskAsync().then(resolve).catch(reject);
})
...
But these are a little indirect. Is there a good way to start a promise chain by just executing a function that could return a promise, and that has errors caught in the ending .catch?
It sounds like you're looking for Promise.try. Promise.resolve is appropriate when you have a value already and want to build a promise chain from it, but if you want to run some code that may throw, use Promise.try instead.
try takes and executes a function, dealing with any synchronous exception in the same manner as then would. You would end up with something like:
bbPromise.try(someTask).then(() => {
return someVar.doSomeOtherTaskAsync();
}).then((result) => {
// Do something with result
}).catch((err) => {
console.log('err: ', err);
});

Categories