View profile

Promises and the Deferred Antipattern

Before ES6 came out, there were dozens of common userland promise implementations. Many used some equ
Promises and the Deferred Antipattern
By Mastering JS Weekly • Issue #26 • View online
Before ES6 came out, there were dozens of common userland promise implementations. Many used some equivalent of the Deferred pattern. The deferred pattern means that your promise isn’t strictly associated with any async operation. A deferred object is simply an object with `resolve()` and `reject()` methods.

Angular 1.x used the Q promise library, and `$q.defer()` created a new promise
Angular 1.x used the Q promise library, and `$q.defer()` created a new promise
Mongoose had its own promise library "mpromise" that used the deferred pattern
Mongoose had its own promise library "mpromise" that used the deferred pattern
There are two key differences between the deferred pattern and modern ES6 promises:
  1. An ES6 promise always has an associated executor function, so ES6 promises always start executing their associated logic immediately. A Deferred doesn’t have any associated logic or an executor function.
  2. An ES6 promise doesn’t expose `resolve()` and `reject()` functions. Only the executor function can resolve or reject the promise, unless you explicitly pull `resolve()` and `reject()` out of the executor function. Since `resolve()` and `reject()` are methods on a Deferred, anybody can resolve or reject a Deferred.
For example, below is how you can implement a Deferred using ES6 promises.
Create a new Deferred using ES6 promises
Create a new Deferred using ES6 promises
Since ES6 promises were released, the Deferred pattern has largely vanished from JavaScript development. I think the Bluebird docs’ rant against Deferred had something to do with it. So what is actually wrong with the Deferred pattern?
The answer is error handling. For example, in the above code, what happens if an error causes the `setTimeout()` to never be called? The promise will be stuck in the pending state forever.
ES6 promises, on the other hand, treat synchronous errors in the executor function as a promise rejection.
ES6 promises handle sync errors
ES6 promises handle sync errors
Things can get even more complicated for the Deferred pattern when you pass a Deferred into multiple functions. Even if you use try/catch to make sure you reject the Deferred if a synchronous error occurs, things get murky when the Deferred is used in different functions. Should you reject the Deferred if there’s an error in a logging function?
Because ES6 promises have an associated executor function, they encapsulate all the logic that goes into determining whether the promise succeeded or failed. The users of your code don’t have to guess if or when they’re responsible for resolving or rejecting a deferred.
Remember, as a consumer of an ES6-promise-based API, you are purely reactive to the promise. You have no way of affecting the promise’s state.
Most Recent Tutorials
Other Interesting Reads
Building a Code Editor with CodeMirror | www.thecodebarbarian.com
What's New in Mongoose 5.9: SchemaType Default Options and Better Populate Limit | www.thecodebarbarian.com
What Tool to Use: webpack vs Gulp vs Grunt vs Browserify ← Alligator.io
Did you enjoy this issue?
Mastering JS Weekly

A weekly summary of our tutorials

If you don't want these updates anymore, please unsubscribe here.
If you were forwarded this newsletter and you like it, you can subscribe here.
Powered by Revue