Hi my name is Guillermo Antony Cava Nuñez, recently while working with sweetalerts2, had to handle some Ajax requests and wanted to make use of the preconfirm methods available. It uses promises in order to wait for the response prior to advancing to the next, thus had to make a promise within the scope of the method. This drove me to refresh and pick up on promises, thus the articles that follows.
What’s a promise?
$.ajax({ url: '/value', success: function(value) { var result = parseInt(value) + 10; setTimeout(function() { console.log(result); }, 5000); } });
…and refactor it into this:
ajax({ url: '/value' }).then(function(value) { return parseInt(value) + 10; }).then( delay(5000) ).then(function(result) { console.log(result); })
then
and catch
. then
takes a continuation, which is just a callback that takes the result as an argument and returns a new promise or any other value. Similarly, catch
is the callback that is invoked when the action raises an exception or fails in some other way.then
encompasses both behaviors:promise.then(onFulfilled, onRejected)
So catch
can be thought of as being defined as follows:
promise.catch(onRejected) := promise.then(null, onRejected)
How do you make a promise?
then
andcatch
methods to make a promise. You should use the Promise
constructor instead:var promise = new Promise(function(resolve, reject) { // maybe do some async stuff in here resolve('result'); });
resolve
callback when your promise is fulfilled, or call reject
if something goes wrong. Equivalently, you can just raise an exception.Promise.resolve(value)
. Or if you want to make a promise that fails immediately, you can write Promise.reject(error)
.Timeouts
setTimeout
function is used to execute some code after a specified delay. I’ve found it’s really useful to define a promise version of it:function delay(milliseconds) { return function(result) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(result); }, milliseconds); }); }; }
This is how you use it:
delay(1000)('hello').then(function(result) { console.log(result); });
You might wonder why delay
is curried. This is so we can sequence it after another promise:
somePromise.then(delay(1000)).then(function() { console.log('1 second after somePromise!'); });
AJAX
$.ajax
isn’t quite compatible with promises, because it doesn’t support catch
. But we can easily create a wrapper:function ajax(options) { return new Promise(function(resolve, reject) { $.ajax(options).done(resolve).fail(reject); }); }
Example:
ajax({ url: '/' }).then(function(result) { console.log(result); });
Or, if you don’t use jQuery, you can make a wrapper for XMLHttpRequest
:
function ajax(url, method, data) { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.responseType = 'text'; request.onreadystatechange = function() { if (request.readyState === XMLHttpRequest.DONE) { if (request.status === 200) { resolve(request.responseText); } else { reject(Error(request.statusText)); } } }; request.onerror = function() { reject(Error("Network Error")); }; request.open(method, url, true); request.send(data); }); }
Example:
ajax('/', 'GET').then(function(result) { console.log(result); });
Deferred execution
new Promise(function(resolve, reject) { console.log('A'); resolve(); }).then(function() { console.log('B'); }); console.log('C');
A
,C
, then B
.then
thunk is deferred, but the one passed to the Promise
constructor isn’t.then
. I often want to defer the execution of some piece of code until after the current synchronous code is finished. I used to use setTimeout(func, 1)
to do this. But now you can make a promise for it:var defer = Promise.resolve();
You can use it like this:
defer.then(function() { console.log('A'); }); console.log('B');
B
, then A
. Although this isn’t any shorter than just using setTimeout(func, 1)
, it clarifies the intention and is composable with other promises. The fact that it can be chained with other promises leads to better structured code.Closing remarks
- The callbacks for
then
andcatch
can return any value, but they behave differently if the value is a promise. Ordinarily, the return value is passed to the nextthen
continuation in the chain. But if the return value is a promise, the promise’s fulfillment value is passed to the continuation instead. This means returningPromise.resolve(x)
is the same as returningx
. - The function passed to the
Promise
constructor is executed synchronously, but any continuations sequenced withthen
orcatch
will be deferred until the next event loop cycle. The latter behavior is whydefer
works. catch
is a reserved keyword used for exception handling in JavaScript, but it’s also the name of the method to attach an error handler to a promise. It seems like an unfortunate naming collision. On the bright side, it’s easy to remember; so maybe the collision isn’t unfortunate after all!
Promise.resolve(value)
).Promise
is a monad! Or at least it would be, if it weren’t for surprise #1.