Promises were a huge step forward for JavaScript. Finally, a mess of nested callbacks could be replaced with a fluent API.
Still, the built-in Promise has some limitations. As a replacement for callback functions, Promises are designed to wrap and represent a single function. When you create a Promise you hand control over to it by giving it two callbacks: resolve
and reject
. Then you wait until the function calls one of them:
new Promise((resolve, reject) => { // do something asynchronous here // call `resolve` or `reject` when finished }) .then(result => { // this code can't execute until `resolve` or `reject` is called above })
This means a standard Promise is simply a more organized way to handle callback results and errors.
That’s a problem because it limits abstraction. If a Promise is only a callback object, we limit it to that particular use case. But Promises can be a more flexible means of control flow. They represent pending action, and they indicate when it is finished.
A Promise can exist on its own without wrapping a callback function. To make this happen, we just need to pull its resolve
and reject
methods outside the scope of its callback:
const defer = () => { let resolve, reject const deferred = new Promise((res, rej) => { resolve = res reject = rej }) deferred.resolve = resolve deferred.reject = reject return deferred }
We now have a Promise
with two additional methods: resolve
and reject
. We can use these together with then
and catch
to control flow any way we want.
Let’s break this down. defer
is a factory function. It creates special Promise
objects, so to use it we would assign the result to a variable:
const myPromise = defer()
We can attach callbacks to myPromise
just like any other Promise
:
myPromise .then(result => { console.log('It finished!', result) }) .catch(err => { console.warn('Something went wrong', err) })
Finally, anywhere in our program that myPromise
is in scope, we can resolve
or reject
it:
if (err) { myPromise.reject(err) } else { myPromise.resolve(value) }
You might have noticed this looks a lot like a jQuery Deferred Object. This pattern gives us much of the same utility value without a library.
I’ll leave it to you to decide if it fits your current work.