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.