Javascript Promises
Since June 2015, promises have arrived in JavaScript with ES6 and can be used natively. Maybe some of you are like me at the beginning and don't quite know what promises are good for or how to use them.
What are promises?
JavaScript is "single-threaded" and therefore works sequentially, i.e. synchronously. In short, promises now run several functions one after another; more precisely: we can call function 2 exactly when function 1 has completed. So we can first perform an action, and only once it has completed, start the next one. This is useful, for example, with an Ajax request: if we want to start processing the data only once the Ajax request has finished, we can use a promise for it, which additionally distinguishes whether the promise succeeded or caused an error. Without a promise, JavaScript would carry on even though the Ajax request hasn't finished, with the result that the return value might still be empty. Promises are therefore only useful for asynchronous activities - for synchronous ones we can rely on JavaScript's sequential processing. Synchronous code is very simple - it is executed from top to bottom, and step 2 always waits until step 1 has completed. So we would always have to wait until an action has finished to perform a following action, which is annoying and above all takes time, which ultimately makes the script take longer to finish. Even worse - if we don't place the JavaScript code at the end of the page, its execution competes with the rendering of the page (JavaScript, CSS, HTML etc. share the same thread - running a script blocks the further building of the web page), so the page builds up only slowly. That annoys the end user, and annoyed end users annoy the developer.
States
A promise can have 4 states:
- Pending: the promise is still in part 1 - so it has neither succeeded nor failed so far
- Fulfilled: operation completed -> run the fulfilled function
- Rejected: an error occurred -> run the rejected function
- Settled: the promise has completed and is either rejected or fulfilled
Why not if and if and if and if...?
Of course we can also nest the code in a lot, lot of IF statements and then get the infamous pyramid of doom. With promises we can avoid that.
Syntax
The syntax of a promise looks like this: new Promise(function(resolve, reject) { / CODE / });. Inside the code you can now call the functions / arguments resolve() (leads to the state "fulfilled" and is called when the code returns no error / the promise was successful) and reject() (leads to the state "rejected" and is called when the code has an error / the promise was not successful).
Basics
A short example would accordingly be:
var promise = new Promise(function(resolve, reject) {
// Insert your code here, which sets success = true on success
success === true ? resolve('Good') : reject('Bad');
});
promise.then(function(result) {
console.log("Success:");
console.log(result);
}, function(result) {
console.log("Error:");
console.log(result);
});
While your code is running, the promise is in the "pending" state; as soon as it has finished it is in the "settled" state, either fulfilled (if success === true) and resolve() is called, or rejected (if success === false) and reject() is called. So the promise constructor receives a callback with two parameters: resolve (action completed successfully) and reject (an error occurred). Once the code has finished (promise.then()), either "Success: Good" or "Error: Bad" is printed, depending on whether resolve() or reject() is called. In plain English: the variable promise contains the promise. Promise.then() takes as parameters the two functions we defined in "new Promise" - that corresponds roughly to promise.then(function resolve(result){}, function reject(result){});. The advantage is that resolve() and reject() are only called once the code in the promise has run. Especially for asynchronous Ajax requests that is useful - we can process the data from the request only once the request has completed.
Asynchronous example code
As the basis for all following examples we take this asynchronous AJAX request.
function GET(target) {
return new Promise(function(success, error) {
var request = new XMLHttpRequest();
request.open("GET", target);
request.addEventListener("load", function() {
if (this.status == 200) {
success('Erfolgreich!');
} else {
error(this.status);
}
}, false);
request.send(target);
});
}
Promising the promise
To make the confusion perfect, resolve() and reject() in turn return a promise, so we can attach several promises to each other and chain them.
GET("file1.html")
.then(GET("file2.html"))
.then(GET("file3.html"));
So we first load file1.html, then file2.html, then file3.html. The "success" function does nothing other than create another promise, but with a different file.
Promise: It will be an array
You don't have to chain several identical promises to each other. For that there is the all() method - it takes several promise objects as an array and runs all the functions in that array as a promise. A small example:
Promise.all([GET("file1.html"), GET("file2.htlm")]).then(
function (success) {
console.log(success);
},
function (error) {
console.log("An error occurred:");
console.log(error);
}
);
All functions contained in the array are now handed to the promise. As soon as an error occurs, error() is called, and only when all functions have been executed successfully is success() called. Alternatively we can also use catch: .catch(function(error) {}); Our example would then look like this:
Promise.all([GET("file1.html"), GET("file2.htlm")]).then(
function(success) {
console.log(success);
}).catch(
function(error) {
console.error(error);
});
Fastest promise
Promise.race() returns only one promise - the one that completed first.
Promise.race([GET("smallfile.jpg"), GET("bigfile.jpg"), GET("extrasmallfile.jpg")]).then(
function (success) {
console.log(success);
}
);
Suppose smallfile is 200mb, extrasmallfile is 2kb, and bigfile is 3gb. In this case success() is called, and indeed after the file extrasmallfile has been fetched. Because all 3 function calls run asynchronously, bigfile doesn't have to wait for smallfile, nor extrasmallfile for bigfile.