A promise is a value that might not exist because the action needed to produce that value has not been carried out. Thus, after the expected action has run, the promise will have a value indicating the success or failure of the action.
Promises were first introduced by Friedman and Wise in their 1976 paper – The Impact of Applicative Programming on Multiprocessing. A lot of languages have implemented the concepts and some examples include the C# Task, Python and Java’s Futures as well as the JavaScript Promise.
JavaScript promises simplify asynchronous programming by avoiding the bane of convoluted callbacks. They can be used for async and synchronous tasks although usage is mostly tied to AJAX requests.
Callback issues
The most popular way of asynchronous programming in JavaScript, using callbacks, has a couple of drawbacks.
1. Error handling issues with callbacks
Callbacks have to accept error handler parameters since there is no way to guarantee the success or failure of an asynchronous operation.
Synchronous error handling constructs like the try/catch can’t be used since async errors might not occur in the body of the try/catch block.
Do you now know why jQuery’s $.ajax has a onerror function?
2. Returning values from callbacks
Callbacks cannot return values – they only accept parameters. The continuation passing style provides a workaround but can easily result in a tangle of callbacks.
If an async operation might fail then error-handling callbacks have to be created at each layer. This leads to error-handling chains that can stretch forever (yeah I am exaggerating but you do get the point).
Promises help to avoid this since they return values that allow execution in a seemingly synchronous way and prevent polluting function signatures to capture success/errors.
The JavaScript Promise
Think of a promise as a state machine – there are specified valid states and transitions.
- Pending – not yet resolved
- Succeeded / Fulfilled – pending operation succeeded
- Failed / Rejected – pending operation failed
A promise starts out in the pending state and can end up in only one of the fulfilled or rejected states. Thus it is either ‘fulfilled’ with some value or fails due to a reason. Success and failure are mutually exclusive events – only one can occur. Whichever of these two happens, the associated callback gets invoked notifying consumers of the operation’s status.
Resolution to success/failed states occurs once – once resolved, a promise never leaves the success/failed state. This rule ensures the immediate invocation of callbacks attached to resolved promises; this prevents deadlocks from happening – imagine a callback ‘waiting’ for an already resolved promise to be resolved.
Here’s how to declare a promise:
var p = new Promise(onresolve, onreject);
var q = new Promise(function(rslv, rjct){..});
And here is a really simple example of using promises
var p = new Promise(
function(onresolve, onreject){
setTimeout(function () {
onresolve("done after 2 seconds");
}, 2000);
});
p.then(function(value) {
console.log(value);
});
//done after 2 seconds
The MDN specification for promises highlights a couple of useful methods including
- Promise.all which allows resolving a list of promises in parallel
- Promise.reject which allows creating rejected promises with a particular value
- Promise.resolve which allows creating resolved promises with a particular value
- Promise.prototype.catch (Instance method) which is syntactic sugar allow you to catch promise rejects.
This was a shallow dive into promises, the next post insha Allaah will cover promise usage patterns.
3 thoughts on “Introduction to JavaScript Promises”