Jordan promises – async/await vs .then

As I’ve stated in a lot of other posts, I’m a big fan of async/await. I think it’s a pretty clean way to manage code your synchronous and asynchronous code.

Async/Await awesomeness

I want to compare some of the bad that can be avoided with async/await.

// Yucky then usage
async function getThePizza() {
	functionGettingFromMongo('someArguments').then((someData) => {
		
		// We want to use this data so we need to know that this promise completed
		doSomeWorkWithAPromise().then((moreData) => {
			const somethingCool = someData + moreData;

			insertToMongoNow(somethingCool).then((response) => {
				console.log('success!', response);
			}).catch((e) => {
				console.log('some error happened', e);
			});
		});

	});
}

So this is a function where we are entering callback hell. While .then() isn’t a callback function, codewise it reads the exact same. It’s a bunch of nested functions that get deeper and deeper and make code less and less readable.

// So good.
async function getThePizza() {
	const someData = await functionGettingFromMongo('someArguments');
	const moreData = await doSomeWorkWithAPromise();

	const somethingCool = someData + moreData;
	try {
		const response = await insertToMongoNow(somethingCool );
		console.log('success!', response);
	}
	catch(e) {
		console.log('some error happened', e);
	}
		
}

It’s a lot more readable. At the least it’s not getting more and more indented. I had been using async/await exclusively since I discovered it so much so that I started thinking of .then() as bad and kind of forgot that the beauty of programming is that almost everything is situational.

When .then() isn’t so bad

When I’m trying to scrape a lot of pages at once I don’t want to be blocked. I don’t use await but instead push the promises into an array and use Promise.all. That will ensure that I know when everything completes so I can close my db connection or puppeteer browser. Like this:

async function getThePizza() {
	
	const promises: any[] = [];
	for (let url of lotsAndLotsOfUrls) {
		promises.push(functionForHandlingTheUrl(url));
	}

	await Promise.all(promises);
	await closeMyDbConnectionOrPuppeteerOrSomething();
}

This works fine. Except for when I want to do something with the specific url after functionForHandlingTheUrl finishes with it. Now what? Using await looks like this:

async function getThePizza() {
	
	for (let url of lotsAndLotsOfUrls) {
		const something = await functionForHandlingTheUrl(url);
		await dbWorkOrSomething(something);
	}

	await closeMyDbConnectionOrPuppeteerOrSomething();
}

Very readable but much slower. I’m no longer taking advantage of any concurrency around I/O threads. So what’s the better alternative? This worked GREAT:

async function getThePizza() {
	
	const promises: any[] = [];
	for (let url of lotsAndLotsOfUrls) {
		promises.push(functionForHandlingTheUrl(url).then(something => {
			// This parts only happens when functionForHandlingTheUrl completes and doesn't block the rest
			return dbWorkOrSomething(something);
		}));

	}

	await Promise.all(promises);
	await closeMyDbConnectionOrPuppeteerOrSomething();
}

Using .then() here makes it so I have one nested function, it’s true, but we are able to do stuff when only that specific iteration is ready. It doesn’t block the rest of the looping so I am still lightning fast. And I am happy.

Update based on comment:

Thomas Grainger left a comment that talked about a way to do the same thing as the above code but stick to an async/await pattern rather than mix them. I tested it out and it really worked great. Here it is:

const lotsAndLotsOfUrls: any[] = [];

for (let i = 0; i < 10000; i++) {
	lotsAndLotsOfUrls.push(`${makeid(5)}---${i}`);
}

// Example of blocking, slower function
// (async () => {
// 	for (let i = 0; i < lotsAndLotsOfUrls.length; i++) {
// 		const something = await handleTheUrl(lotsAndLotsOfUrls[i]);
// 		await dbWorkOrSomething(something);
// 	}
// })();

// Example of non blocking, fast function without using then
const promises = lotsAndLotsOfUrls.map(async (url) => {
	const something = await handleTheUrl(url);
	await dbWorkOrSomething(something);
});

async function handleTheUrl(url: string): Promise<any> {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(url + ' ****handled');
		}, 1000 * Math.random());
	});
}

async function dbWorkOrSomething(something: string) {
	console.log('handling something with a db', something);
	return Promise.resolve();
}

function makeid(length: number) {
	let result = '';
	const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
	const charactersLength = characters.length;
	for (let i = 0; i < length; i++) {
		result += characters.charAt(Math.floor(Math.random() * charactersLength));
	}
	return result;
}

Here’s a screenshot of the results. The above is with the non blocking function while the bottom has the slower, blocking function. Thanks Thomas!

Small PS. A lot of my featured images have been from Unsplash.com. It’s a REALLY great resource. Today’s is from Connor Jolley. Thanks Connor!

Looking for business leads?

Using the techniques talked about here at javascriptwebscrapingguy.com, we’ve been able to launch a way to access awesome business leads. Learn more at Cobalt Intelligence!

Leave a Reply

Your email address will not be published. Required fields are marked *