- 4 minutes read

Async/await simplifies your JavaScript code tremendously. But what if you're stuck with an old API using callbacks? You can easily convert it to modern JavaScript with a few lines of code.

This option comes in particularly handy with PrimeNG dialogs. Their callback API is annoying because a modal dialog is just that: it's a blocking dialog. You want to wait until the user reacts, and in the meantime, you can't do anything useful. So there's no point in putting up with callbacks, observables, and subscriptions you have to cancel later. This is the code we'd like to use:

const userAgrees: boolean = await this.dialogService.open(YesOrNoDialog, { header: 'Do you agree?', width: '70%' });

Wrapping the callback in a closure

Before showing you the solution, you probably need some context. PrimeNG implements modal dialogs like so:

const ref = this.dialogService.open(YesOrNoDialog, { header: 'Do you agree?', width: '70%' }); const subscription = ref.onClose.subscribe( (userAgrees: boolean) => { subscription.unsubscribe(); if (userAgrees) { // do something } else { alert("Sorry, I can't proceed without your agreement."); } } );

What I dislike about this code is that it's non-linear. It sort of an out-of-order execution, as I've described in much detail last year.

Luckily, that's easy to fix. It's just a bit cumbersome. You gain a clean API by adding some boiler-plate code to the implementation of the API:

function openDialog(dialog: Type, config: DynamicDialogConfig): Promise { return new Promise((resolve, reject) => { const ref = this.dialogService.open(dialog, config); const subscription = ref.onClose.subscribe( (userAgrees) => { subscription.unsubscribe(); resolve(userAgrees); } ); }) }

Now you can await the user's decision:

const userAgrees = await this.dialogService.open(YesOrNoDialog, { header: 'Do you agree?', width: '70%' } );

Error handling

Of course, you can also translate rejections into an exception:

function readFileAsync(file: String): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { resolve(reader.result); }; reader.onerror = reject; reader.readAsArrayBuffer(file); }) }

Just in case you're new to JavaScript: reader.onerror = reject is cutting a few corner. In most cases, you'll have to write it more verbosely, with the bonus of being more intelligible:

reader.onerror = (event: ProgressEvent) => reject(event);

Getting sleepy?

Finally, here's a bit I look up frequently on StackOverflow. JavaScript doesn't offer a sleep(milliseconds: number) method out-of-the-box, but you can easily build one:

function sleep(milliseconds:number): Promise { return new Promise((resolve, reject) => { setTimeout(() => resolve(), milliseconds); }) }

Now you can wait a while until your background tasks finish. Judging from my experience, you don't want to do this. When I'm tempted to implement and call sleep(), I've almost always messed up some asynchronous API, or I'm using such an API incorrectly. Be that as it may, here's how to sleep:

await sleep(2000); // two seconds of a power nap

Comments