In Node.js, what's the correct way to handle an error using async / await? - node.js

Currently, I have a function as follows:
export async function getFiles (param: any) {
try {
const results = await ... // code to get results from database;
const files = [];
for (let i = 0; i < results.length; i++) {
files.push(results[i]);
}
return files;
}
catch (error) {
console.log(error);
}
}
Separately, I use the library function as follows:
const files = await lib.getFilesAsync(param);
for (const file of files) {
// process file here
}
How do I properly add error handling to this code?
I've tried returning an error in catch:
catch (error) {
console.log(error);
return new Error("getFiles error");
}
but then I get the following error at compile time:
Type 'Error' must have a '[Symbol.iterator]()' method that returns an iterator. (2488)

An async function returns a promise. So, inside that async function you have to make a design decision about whether you want an error to return a rejected promise or some sort of specific error that you can test for. In either case, the caller of the async function needs to do error checking. Here are two possible ways to implement it. It's really your design choice which way to go.
It's somewhat analogous to a synchronous function that can either return a sentinel value like null that must be tested for by the caller when it has an error or it can throw an exception that would be caught by the caller with try/catch.
Caller uses .catch() or try/catch
export async function getFiles (param: any) {
// if any await operation here rejects, the promise returned
// from getFiles() will reject and the caller can .catch() the error
const results = await ... // code to get results from database;
const files = [];
for (let i = 0; i < results.length; i++) {
files.push(results[i]);
}
return files;
}
// usage
getFiles(...).then(files => {
console.log(files);
}).catch(err => {
console.log(err);
})
// or, you could use it this way
try {
let files = await getFiles(...);
console.log(files);
} catch(e) {
console.log(e);
}
Caller checks for error on resolved promise
export async function getFiles (param: any) {
try {
const results = await ... // code to get results from database;
const files = [];
for (let i = 0; i < results.length; i++) {
files.push(results[i]);
}
return files;
}
catch (error) {
console.log(error);
return new Error("getFiles error");
}
}
// usage:
getFiles(...).then(files => {
console.log(files);
if (files instanceof Error) {
// error
} else {
// process files array here
}
});
// or, you could use it this way
let files = await getFiles(...);
console.log(files);
if (files instanceof Error) {
// error
} else {
// process files array here
}
Personally, I prefer to use the rejected promise because it's a lot easier to sequence or chain async operations if you let the promises manage the error propagation for you.

Related

Need to confirm how callbacks and errors work in async.each with try-catch blocks

I've been tinkering with this for a few days now and have seen a number of different patterns. In some ways I feel more confused than I did when I began!
itemsArr is a list of item objects (itemObj) with summary information about each item. Each itemObj contains an itemId which doubles as the API slug directory. So, I need to iterate through the itemsArr, make an API call for each item, and return the updated array with all of the details that were retrieved from each API call. When this is finished, I want to log the enriched array, enrichedItemsArr to persistant storage.
It does not matter in what order the API calls return, hence using async.each. I also don't want to interrupt the execution if an error occurs. My questions:
'Done enriching array' is printing before execution of enrichArr() -> why is await async.each... in enrichArr() not blocking?
I am getting TypeError: callback is not a function in the inner try-catch. Not sure why.
If I pass err to callback() in the inner try-catch, will that halt execution?
Should I pass itemsArr to processDone as the 2nd argument? Is there a way to return itemsArr to main() from the processDone() method?
Does err passed to the final callback contain an array of errors?
const main = async () => {
const itemsArr = items.getArr(); // --> retrieves locally cached itemsArr
const enrichedItemsArr = await enrichArr(itemsArr); // --> handling the async iterator stuff below
await logToDB(enrichedItemsArr); // --> helper function to log enriched info to database
console.log('Done enriching array');
};
const enrichArr = async (itemsArr) => {
// Outer try-catch
try {
const processItem = async (item, callback) => {
// Inner try-catch
try {
const res = await doSomethingAsync(itemID);
item.res = res;
callback(); // --> currently getting `TypeError: callback is not a function`
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
const processDone = (err, itemsArr) => {
if (err) console.error(err); // --> Is err an array of errors or something?
return itemsArr; // --> how do I return this to main()?
};
await async.each(itemsArr, processItem, processDone);
} catch (err) {
throw err; // --> if async.each errors, throw
}
};
Hope this is a good answer for you.
why is await async.each... in enrichArr() not blocking?
Based on the docs, the async.each will return a promise only if the callback is omitted
each
You're including the callback, the async.each won't return a promise and won't block your code using async/await
I am getting TypeError: callback is not a function in the inner try-catch. Not sure why.
Your processItem should be a plain function, doing that I was able to use callback, seems that the library is not happy when you use async functions
const processItem = (item, callback) => {
const itemId = item;
// Inner try-catch
try {
doSomethingAsync((res) => {
item.res = res;
callback()
});
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
If I pass err to callback() in the inner try-catch, will that halt execution?
Yes, it will throw an error
Should I pass itemsArr to processDone as the 2nd argument? Is there a way to return itemsArr to main() from the processDone() method?
If you want to let know the main method that needs to wait, you won't be able to use processDone.
ItemsArr is an object, you can mutate the object and the main method should be able to see those changes, there is no other way if you want to use array.each.
Maybe there is another method in the async library that allows you to return a new array.
Maybe Map is a good option map
Does err passed to the final callback contain an array of errors?
No, it's a way to let the library know that needs to throw an error.
I created a snippet to allow you to play with the code
const async = require('async');
const logToDB = async (items) => {
items.forEach((item) => console.log(JSON.stringify(item)))
}
const doSomethingAsync = (callback) => {
setTimeout(() => {
console.log('processing data')
callback()
}, 1000);
}
const main = async () => {
const itemsArr = [
{
itemId: '71b13422-2582-4975-93c9-447b66764daf'
},
// {
// errorFlag: true
// },
{
itemId: '8ad24197-7d30-4514-bf00-8068e216e90c'
}
]; // --> retrieves locally cached itemsArr
const enrichedItemsArr = await enrichArr(itemsArr); // --> handling the async iterator stuff below
await logToDB(enrichedItemsArr); // --> helper function to log enriched info to database
console.log('Done enriching array');
};
const enrichArr = async (itemsArr) => {
// Outer try-catch
try {
const processItem = (item, callback) => {
console.log('item: ', item);
const itemId = item;
// Inner try-catch
try {
if (item.errorFlag) {
return callback('Test error');
}
doSomethingAsync((res) => {
item.res = res;
callback()
});
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
await async.each(itemsArr, processItem);
return itemsArr;
} catch (err) {
console.log('Error occurred');
throw err; // --> if async.each errors, throw
}
};
main();

How to call a custom function synchronously in node js?

I am developing a server project which needs to call some functions synchronously. Currently I am calling it in asynchronous nature. I found some similar questions on StackOverflow and I can't understand how to apply those solutions to my code. Yet I tried using async/await and ended up with an error The 'await' operator can only be used in an 'async' function
Here is my implementation
function findSuitableRoom(_lecturer, _sessionDay, _sessionTime, _sessionDuration, _sessionType){
let assignedRoom = selectRoomByLevel(preferredRooms, lecturer.level, _sessionDuration); <------- Need to be call synchronously
if (!assignedRoom.success){
let rooms = getRooms(_sessionType); <------- Need to be call synchronously
assignedRoom = assignRoom(_lecturer.rooms, _sessionDuration, _lecturer.level);
} else {
arr_RemovedSessions.push(assignedRoom.removed)
}
return assignedRoom;
}
function getRooms(type){
switch (type){
case 'Tutorial' : type = 'Lecture hall'
break;
case 'Practical' : type = 'Lab'
break;
default : type = 'Lecture hall'
}
Rooms.find({type : type},
(err, rooms) => {
if (!err){
console.log('retrieved rooms ' + rooms)
return rooms;
}
})
}
Here I have provided only two methods because full implementation is very long and I feel if I could understand how to apply synchronous way to one method, I can manage the rest of the methods. Can someone please help me?
Well yes await is only available inside an async function so put async infront of findSuitableRoom.
Also you did a classic mistake. You use return inside of a callback function, and expect getRooms to return you some value.
async function findSuitableRoom(
_lecturer,
_sessionDay,
_sessionTime,
_sessionDuration,
_sessionType
) {
let assignedRoom = selectRoomByLevel(
preferredRooms,
lecturer.level,
_sessionDuration
);
if (!assignedRoom.success) {
try {
let rooms = await getRooms(_sessionType);
} catch (err) {
console.log("no rooms found");
}
assignedRoom = assignRoom(
_lecturer.rooms,
_sessionDuration,
_lecturer.level
);
} else {
arr_RemovedSessions.push(assignedRoom.removed);
}
return assignedRoom;
}
Also wrap it in an try / catch
Since .find() returns an promise if you dont pass an callback you can write it like this
function getRooms(type) {
switch (type) {
case "Tutorial":
type = "Lecture hall";
break;
case "Practical":
type = "Lab";
break;
default:
type = "Lecture hall";
}
return Rooms.find({ type });
}
Note here findSuitableRoom is no longer synchronouse. Its async and returns an promise. That means you will need to use the function like this:
findSuitableRoom.then(res => { console.log(res); })
The 'await' operator can only be used in an 'async' function
This means whenever you want to use the await keyword it needs to be inside a function which has an async keyword (returns promise)
read this https://javascript.info/async-await for more info
const add = (a, b) => {
return new Promise((resolve, reject) => {
setTimeout(() => { //to make it asynchronous
if (a < 0 || b < 0) {
return reject("don't need negative");
}
resolve(a + b);
}, 2000);
});
};
const jatin = async () => {
try{
const sum = await add(10, 5);
const sum2 = await add(sum, -100);
const sum3 = await add(sum2, 1000);
return sum3;
} catch (e) {
console.log(e);
}
};
jatin()
Try this
let's understand this
add is a normal function that does some asynchronous action like
waiting for 2 seconds
normally we use await with async function so in order to use it we make as async function jatin and use await with add function call
to make it synchronous, so until first await add call() doesn't
happen it wont execute another await add call().
Example code if you will use in your app.js
router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(
req.body.email,
req.body.password
);
const token = await user.generateToken();
res.status(200).send({
user,
token,
});
}
catch (error) {
console.log(error);
res.status(400).send();
}
});

insert document into multiple instance of couch DB in Node JS with all success and failure result in any way possible

i have array of db like
const dbArr = ["http://localhost:5984", "http://xyz_couchdb.com:5984"]
data to insert
let data ={
_id: 324567,
name: Harry,
gerder: male
}
here is the logic i am using nano module
return new Promise((resolve, reject) => {
let res = [];
let rej = [];
let counter = 0;
for(let i = 0; i < dbArr.length ; i++){
dbArr[i].insert(data, (err, body) => {
err ? rej.push(err) : res.push(body)
if(counter === obj.dbArray.length -1){
rej.length ? reject(rej) : resolve(res)
}
counter++;
})
}
})
what can be the best possible way to achieve this using promise or async module or anything.
In the following example, we gotta use Array.map to create one promise for each element of dbArr, then we gotta wait all promises to end using Promise.all. The catch is here so we handle the errors.
function getAll(dbArr) {
return Promise.all(dbArr.map(x => x.insert(data)));
}
getAll(dbArr)
.then((rets) => {
// Handle the returns
// They are in an array
})
.catch((err) => {
// Handle the error
});
EDIT :
Ok after checking out the documentation of node-couchdb (the one I suppose you use) - I saw that the .insert() method do not return a Promise but only a callback.
So we gotta transform the method, so it will return a Promise using util.Promisify()
const {
promisify,
} = require('util');
function getAll(dbArr) {
return Promise.all(dbArr.map(x => promisify(x.insert)(data)));
}
getAll(dbArr)
.then((rets) => {
// Handle the returns
// They are in an array
})
.catch((err) => {
// Handle the error
});

For loop in promise.then()?

I need to iterate between two values and create/touch files (I/O) on each iteration.
I'm using the fs-promise module to do so asynchronously:
const path = require('path');
const fsp = require('fs-promise');
function addPages(startAt, pages, mode) {
let htmlExt = mode.HTML;
let cssExt = mode.CSS;
fsp.readFile(path.join('.', 'templates', 'body.html'), { encoding: 'utf-8' })
.then((content) => {
// return Promise.all(() => {}).then().catch(); // Do this.
for (let i = startAt, endAt = startAt + pages; i < endAt; i++) {
console.log(i);
fsp.writeFile(path.join('.', 'manuscript', `page-${i}`, `style.${cssExt}`), '')
.then(() => { console.log('Yay!') })
.catch(console.log.bind(console));
// fsp.writeFile(path.join('.', 'manuscript', `page-${i}`, `style.${cssExt}`), '')
// .then((i, templateHTML) => {
// fsp.writeFile(path.join('.', 'manuscript', `page-${i}`, `body.${htmlExt}`), content);
// })
// .catch((err) => {
// console.log.bind(console);
// });
}
})
.catch((err) => {
if (err) return error('Couldn\'t create pages', err);
});
Now I did read that Promises.all([Array of promises]) is the way to go for looping inside the then() scope, but the question is why/how?
I'm unable to wrap my head around why the for-loop doesn't execute before the context moves out of the promised then() scope, and then how should I get to the expected outcome.
const path = require('path');
const fsp = require('fs-promise');
function addPages(startAt, pages, mode) {
let htmlExt = mode.HTML;
let cssExt = mode.CSS;
return fsp.readFile(path.join('.', 'templates', 'body.html'), { encoding: 'utf-8' })
.then((content) => {
var pendingWrites = [];
for (let i = startAt, endAt = startAt + pages; i < endAt; i++) {
let filename = path.join('.', 'manuscript', `page-${i}`, `style.${cssExt}`);
let thisWrite = fsp.writeFile(filename, '');
pendingWrites.push(thisWrite);
}
return Promise.all(pendingWrites);
})
.catch((err) => {
// either fully recover from the error or rethrow
console.log("Could not add pages: ", err);
throw err;
});
}
As elaborated in the comments, resist the temptation to introduce none-functional .catch() handlers into your promise chain.
Non-functional means in this case: It does not recover from the error and does not rethrow the error. A catch handler that does not throw marks an error as handled, i.e. it returns a resolved promise, not a rejected one. This makes proper error handling later in the promise chain impossible. It's bad practice and unhelpful.
If you want to log the error, log it and rethrow it. If you have fully recovered from the error and subsequent code is unimpeded, don't rethrow.

Async/Await not waiting

I'm running into an issue which I don't fully understand. I feel like there are likely concepts which I haven't grasped, code that could be optimized, and possibly a bug thrown in for good measure.
To greatly simplify the overall flow:
A request is made to an external API
The returned JSON object is parsed and scanned for link references
If any link references are found, additional requests are made to populate/replace link references with real JSON data
Once all link references have been replaced, the original request is returned and used to build content
Here, is the original request (#1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
Store.get is represented by:
async get(type, id) {
return await this._get(type, id);
}
Which calls:
_get(type, id) {
return new Promise(async (resolve, reject) => {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if(isAsset(data)) {
resolve(data);
} else if(isEntry(data)) {
await this._scan(data);
resolve(data);
} else {
const error = 'Response is not entry/asset.';
console.log(error);
reject(error);
}
});
}
The API call is:
_api(type, id) {
return new Promise((resolve, reject) => {
Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => {
if(error) {
console.log(error);
reject(error);
} else {
data = JSON.parse(data);
if(data.sys.type === Constants.Contentful.ERROR) {
console.log(data);
reject(data);
} else {
resolve(data);
}
}
});
});
}
When an entry is returned, it is scanned:
_scan(data) {
return new Promise((resolve, reject) => {
if(data && data.fields) {
const keys = Object.keys(data.fields);
keys.forEach(async (key, i) => {
var val = data.fields[key];
if(isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if(isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
if(i === keys.length - 1) {
resolve();
}
});
} else {
const error = 'Required data is unavailable.';
console.log(error);
reject(error);
}
});
}
If link references are found, additional requests are made and then the resulting JSON is injected into the original JSON in place of the reference:
_inject(fields, key, index, data) {
if(isNaN(index)) {
fields[key] = data;
} else {
fields[key][index] = data;
}
}
Notice, I'm using async, await, and Promise's I believe in their intended manor. What ends up happening: The calls for referenced data (gets resulting of _scan) end up occurring after the original request is returned. This ends up providing incomplete data to the content template.
Additional information concerning my build setup:
npm#2.14.2
node#4.0.0
webpack#1.12.2
babel#5.8.34
babel-loader#5.4.0
I believe the issue is in your forEach call in _scan. For reference, see this passage in Taming the asynchronous beast with ES7:
However, if you try to use an async function, then you will get a more subtle bug:
let docs = [{}, {}, {}];
// WARNING: this won't work
docs.forEach(async function (doc, i) {
await db.post(doc);
console.log(i);
});
console.log('main loop done');
This will compile, but the problem is that this will print out:
main loop done
0
1
2
What's happening is that the main function is exiting early, because the await is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.
The lesson is: be careful when you have any function inside your async function. The await will only pause its parent function, so check that it's doing what you actually think it's doing.
So each iteration of the forEach call is running concurrently; they're not executing one at a time. As soon as the one that matches the criteria i === keys.length - 1 finishes, the promise is resolved and _scan returns, even though other async functions called via forEach are still executing.
You would need to either change the forEach to a map to return an array of promises, which you can then await* from _scan (if you want to execute them all concurrently and then call something when they're all done), or execute them one-at-a-time if you want them to execute in sequence.
As a side note, if I'm reading them right, some of your async functions can be simplified a bit; remember that, while awaiting an async function call returns a value, simply calling it returns another promise, and returning a value from an async function is the same as returning a promise that resolves to that value in a non-async function. So, for example, _get can be:
async _get(type, id) {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if (isAsset(data)) {
return data;
} else if (isEntry(data)) {
await this._scan(data);
return data;
} else {
const error = 'Response is not entry/asset.';
console.log(error);
throw error;
}
}
Similarly, _scan could be (assuming you want the forEach bodies to execute concurrently):
async _scan(data) {
if (data && data.fields) {
const keys = Object.keys(data.fields);
const promises = keys.map(async (key, i) => {
var val = data.fields[key];
if (isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if (isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
});
await* promises;
} else {
const error = 'Required data is unavailable.';
console.log(error);
throw error;
}
}

Resources