Since most browsers now support the latest ES6 (and above) features natively (along with Node.js, as well), the best way is to use async/await . The code you get is really clean and readable.
Let's take a look at some examples.
The inevitable truth about programming is that the speed at which new features are released vs. the speed at which they are incorporated into old packages, modules, or codebase doesn't match. Ever. For that reason, we do have a lot of packages on npm which still use the old callback(err, data) approach. However, there is a pretty simple way to turn those into async/await-compliant functions:
const promisify = function() {
const [ fn, ...args ] = Array.from( arguments );
return new Promise( ( resolve, reject ) => {
fn( args, ( err, data ) {
if ( err ) { reject( err ); }
else { resolve( data ); }
}
}
};
Now, of course, that is a very basic example (read: do not use this in production); but you get the point.
With that, suppose we have the following function:
const doStuff = ( param1, cb ) => {
setTimeout( () => {
cb( `The data is ${ param1 }` );
}, 2000 );
};
We can get a promise by doing:
const doStuffPromisify = promisify( doStuff, 'Hello' );
async/await is, in a way, a wrapper around promises. Suppose we have the following function:
const doStuffPromises = param => {
return new Promise( ( resolve, reject ) => {
setTimeout( () => {
resolve( `The data is ${ param }` );
}, 2000 );
}
};
I won't go into the way of using this using the .then()/.catch() blocks; let's use async:
const executeFunction = async () => {
try {
const data = await doStuffPromises( 'Hello' );
console.log( data );
} catch (error) { console.error( error ); }
}
executeFunction();
The try block acts like the .then() chain; if we convert this to the .then() chain, it'll look like this:
doStuffPromises( 'hello' ).then( console.log ).catch( console.error );
Not convinced? Let's chain it.
doStuffPromises( 'hello' )
.then( doStuffPromisify )
.then( console.log )
.catch( console.error );
Or:
const executeFunction = async () => {
try {
const data = await doStuffPromises( 'Hello' );
const data2 = await doStuffPromisify( data );
console.log( data2 );
} catch (error) { console.error( error ); }
}
executeFunction();
A lot simpler, right?
It's always better to use a bit of the native .then() chain if the code is as simple as above. However, with growing complexity, you might want to use the try/catch with async/await.