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?
thenandcatch 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.thenthunk 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
thenandcatchcan return any value, but they behave differently if the value is a promise. Ordinarily, the return value is passed to the nextthencontinuation 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
Promiseconstructor is executed synchronously, but any continuations sequenced withthenorcatchwill be deferred until the next event loop cycle. The latter behavior is whydeferworks. catchis 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.