Catch Errors in Nested Awaits
Adam C. |

TryCatch 101

TryCatch is very useful in JavaScript code to handle errors/exceptions. Without that, our code will die in the middle when there is an exception. For example,

let size = undefinedVariable.size;
console.log("The size is: ", size);

When running this code, we will get the error: 

Uncaught ReferenceError: undefinedVariable is not defined

And the console.log will not be executed. However, if we add TryCatch, then the error will be properly caught, and the rest code will continue running. For example:

let size = 0;

try {
  size = undefinedVariable.size;
} catch (error) {
  console.log("Error caught: ", {error});
}

console.log("The size is: ", size);

Now, we should see both “Error caught …” and “The size is 0” on your console.

Photo by Blake Wisz on Unsplash

TryCatch in Async/Await

Using the same idea, we can use TryCatch for Async/Await, like below:

async function exportAllPages(){
	//load pages in the database, process, and export them into JSON files
}
async function run() {
    try {
        await exportAllPages();
    } catch (e) {
        console.error(e);
    } finally {
        sendMail('All pages have been exported, but there could be some errors');
    }
}

When we execute the code above, exportAllPages() will run and load the pages from the database, process, and export into the JSON, and then after the process is done,  it sends the notification via email. 

Now, let's assume there is some exception when exporting all pages since we have TryCatch, so we should still receive the email when it's done, but we don't know if all the pages have been exported or not. To fix that, we can add some logic to our run() function, like below:

async function run() {
	const error = "";
    try {
        await exportAllPages();
    } catch (e) {
    	error += "exprotAllPages failed: " + e;
        console.error(e);
    } finally {
    	if(error){
        	sendMail('Exporting all pages is finished, but we got some error:" + error); );
   		}
   		else {
   			sendMail('All pages have been exported!');
   		}
    }
}

If exportAllPages() is a simple process, TryCatch above is probably good enough to tell us which where the exception happened, but in most cases, this won't help too much.  Also, if you have 1000 pages to export, and the exception occurs when in the middle, the rest of the pages would not be processed.

Furthermore, in the real world, the exportAllPages() could contain many nested Async/Await functions, so TryCatch could only catch the first exception bubbled up from the nested await functions.  Ideally, we should add TryCatch to all inner Async/Await functions, and save all exception errors into the Global variable, and then include that to the sendMail(), so then we are able to which pages failed to export for what reasons, and make sure all rest pages are still exported.

So the export function could be like this:

// check https://deniapps.com/blog/how-to-use-global-variable-in-es6-modules
// to learn more about globalContext
import globalContext from "./globalContext";

async function exportAllPages(){
	//load pages in the database
	const pages = [];
	try {
		await dbContext('pages').select('id').where(...);
	}
	catch (e) {
		globalContext.error.concat(e);
	}
	const promises = pages.map((page) => exportCustomPage(page.id));
	return await Promise.all(promises);	
}

And then in the nested Await function: exportCustomPage()

import globalContext from "./globalContext";

async function exportCustomPages(pageID){
	//load pages in the database
	const pages = [];
	try {
		const pageData = await dbContext('pages').select('*').where({id: pageId});
		await saveDatatoJSON(pageData);
	}
	catch (e) {
		globalContext.error.concat("export page,", pageID, e);
	}
}

So, if there is an issue to export JSON for a page, then the TryCatch will catch the error into the global variable, and keep the rest process running.

In Summary

Adding TryCatch at the top level of Await/Async may meet our need to prevent the code from interrupting in the middle, but in order for debugging the cause of the error, then we would need to add the TryCatch to the nested Await/Async functions.