Async Transport
Provides support for 1-n asynchronous, returning a predictable object for easy consumption.
Contents
Pre-requisites
You need to use Node 7.6 (or later) or an ES7 transpiler to use async/await functionality. You can use babel or typescript for that.
Install
npm i async-transport --save
or using yarn
yarn add async-transport
Why use asyncTransport?
ES7 async/await allows us to write asynchronous code in a style that looks synchronous. This is great, but under the hood, it is crucial to remember that we are still effectively dealing with Promises. When implementing error handling inside Promises developers commonly use a try/catch approach like the following.
{ try const user = await UserModel; if!user return ; catche return ; try const savedTask = await ; catche return ; ifusernotificationsEnabled try await NotificationService; catche return ; ifsavedTaskassignedUserid !== userid try await NotificationService; catche return ; ;}
This becomes onerous, for example, when writing API endpoints that assemble data from multiple sources and may quickly become hard to read (and maintain). When researching ways to remedy this, I found this Medium blog entry written by Dima Grossman and began using the resulting await-to-js package. This yielded code like so.
; { let err user savedTask; err user = await ; if!user throw 'No user found'; err savedTask = await ; iferr throw 'Error occurred while saving task'; ifusernotificationsEnabled const err = await ; if err console; }
This was an improvement, but still did not fully meet my needs. In more complex use-cases, one still needed to manage and track multiple arrays as a result of wrapping async code in to
method calls.
What if we could utilize a single method to handle the execution of 1-n async functions, using to
behind the scenes to keep things cleanly organized asyncTransport
was my answer to this question.
Utilizing asyncTransport
, the above code may be further simplified like so.
; { let result = await ; // Print (or throw) errors by iterating over the errors collection if resulthasErrors for let i = 0; i < resulterrorslength; i++ if resulterrorsi console; // Otherwise do stuff with the result data else console; }
Using this approach, we only have to track one variable, result
, which will reliably contain three properties, hasErrors
, errors
, and data
.
errors
and data
are parallel arrays, both of which maintain the order of the functions provided. In other words, executing asyncTransport([fn1, fn2, fn3])
will return an object whose errors
contain [errors-from-fn1, errors-from-fn2, errors-from-fn3]
as well as a data
array that contains [data-from-fn1, data-from-fn2, data-from-fn3]
.
Details
The asyncTransport
function takes two arguments
promiseCollection
- an array of functions, if a single fn is passed, it will be converted to an array with a single element.options
- an object containing params that affectasyncTransport
's execution. Currently only thestrategy
param is supported (see more on this below in the usage examples).
Once all functions in the promiseCollection
have either resolved or errored (or some combination of the two), an object is returned containing 3 props. These props are
hasErrors
-true
if errors were detected,false
otherwiseerrors
- an array of errors organized in the order in which the related functions were passeddata
- an array of data objects organized in the order in which the related functions to were passed
Usage Examples
While these examples are clearly trivial and fabricated, they should clearly demonstrate how asyncTransport
may be integrated into your work-flow.
Basic Examples
If you are concerned with only one async fn, you could write something like the following.
const asyncTransport = ; // One mock async fnlet { let deferred = { }; return deferred;} // Working with promises via async/awaitlet foo = async { let theData = await ; console;} ; // The above code logs the following object to the console hasErrors: false errors: null data: foo: 'bar'
If you have multiple async fns simply pass an array to asyncTransport
like so.
const asyncTransport = ; // Two mock async fnslet { let deferred = { }; return deferred;} let { let deferred = { }; return deferred;} // Working with promises via async/awaitlet foo = async { let theData = await ; console;} ; // Above code logs the following object to the console hasErrors: false errors: null null data: foo: 'bar' baz: 'boom'
Serial Execution Examples
By default, asyncTransport
will execute async fns passed to it (nearly) simultaneously and items will resolve in whatever order they resolve.
If each function relies on the previous fn to have successfully resolved, you can pass a second parameter to asyncTransport
to enforce serial execution of async functions. Let's modify our example above to see how we do this.
const asyncTransport = ; let { let deferred = { }; return deferred;} let { let deferred = { }; return deferred;} let foo = async { let theData = await ; console;} ; // Above code still logs the following object to the console, but// output in this case is identical to the `parallel` strategy hasErrors: false errors: null null data: foo: 'bar' baz: 'boom'
Data Dependent Serial Execution Examples
In addition to guaranteed order of execution, asyncTransport
also provides a way to pass results from prior async function calls to subsequent functions as arguments. Consider the following.
const asyncTransport = ; let { let deferred = { }; return deferred;} // args parameter is the data object returned from the prior async fnlet { let deferred = { }; return deferred;} let foo = async { let theData = await ; console;} ; // Above code logs the following object to the console hasErrors: false errors: null null data: val: 4 val: 8
Given the above, ideally one should add safeguards - like checking that args
and any keys you plan on using in your fn actually exist - but as written, it should be adequate to illustrate the process.
License
MIT © Joseph (Jos) Smith