DRAFT
Errors and Asynchronisity
11 MayAn investigation into a handful of programming styles and their impact on the error execution paths.
First the code.
(in javascript)
C-style Return Values
function removeRecord(id) {
let error = remove(id)
if (error) {
if (error instanceof NotFoundError) {
// ignore
} else {
// notify the *caller* about the error
return error
}
}
// ...
}
Callbacks
function removeRecord(id, callback) {
remove(id, (error, _) => {
if (error) {
if (error instanceof NotFoundError) {
// ignore
} else {
// notify the *caller* about the error
callback(error)
}
}
// ...
})
}
Async/await
async function removeRecord(id) {
try {
await remove(id)
} catch(error) {
if (error instanceof NotFoundError) {
// ignore
} else {
// notify the *caller* about the error
throw error
}
}
// ...
}
RxJS Observables as Reducers
function removeRecord(id) {
remove$.next(id)
// the caller is notified about errors that
// occur *while calling remove.next()*
}
// elsewhere
afterRemove$ = remove$.pipe(
switchMap(_ => {
// prevents the "infinite stream" from completing
return of(_).pipe(
// captures errors *while calling remove()* into the stream
switchMap(_ => remove(id)),
catchError(error => {
if (error instanceof NotFoundError) {
// ignore
return EMPTY
} else {
// notify the *observer* about error
return throwError(error)
}
})
// ...
)
})
)
RxJS Observables as Return Values
function removeRecord(id) {
// capture errors *while calling remove()* into the stream
return of(null).pipe(
switchMap(_ => remove(id)),
catchError(error => {
if (error instanceof NotFoundError) {
// ignore
return EMPTY
} else {
// notify the *caller* about errors
return throwError(error)
}
})
// ...
)
}
The Differences
- C-style Return Values
- Simple
- Errors are always available to caller
- Doesn’t handle asynchronous results (on its own)
- Callbacks
- Functional
- Errors are always available to caller
- Sometimes leads to “callback hell”
- Async/await
- Typical
- Simple
- Errors are always available to caller
- RxJS Observables as Reducers
- Two error paths
- Relies on “infinite streams”
- Only some errors are available to caller
- RxJS Observables as Return Values
- Two error paths
- No “infinite streams”
- Errors are always available to caller
Notes:
Async/await unwinds the stack on error, so callers always have access to the errors. Callbacks not defined by the caller effectively hides the error from the initiator. This is sometimes appropriate, but not the normal. Callbacks, especially with RxJS, can thus create a “feed-forward” error path which excludes the caller.