Fallback Actions in ES6 Promise
It all started with the fact that as a test task at interviews, I started asking applicants to implement a preloader of pictures on JS. In addition to the preload itself, the script should be able to substitute a fallback image if the desired image could not be loaded. A prerequisite was the use of ES6 Promise.
Then I thought: “Why not implement such a preloader yourself and put it into general use? Yes, this is also an excellent reason to write an article on Habr! ”
Actually, under the cutter, a description of the logic of such a preloader + a link to the preloader itself.
To begin with, let's recall what a promise in JS is.
About promises
Promis is, first of all, a way of organizing asynchronous code. Although not necessarily asynchronous ...
To create a promise, you need a function that will be executed immediately after creating the promise.
Our task, inside this function, is to call one of the two methods passed to this function automatically - resolve or reject.
By calling one of these methods, we tell the promise about the status of the task: completed successfully (resolve) or failed (reject).
This is done in order to be able to build a chain of further actions in the event of a successful or unsuccessful task.
var p = new Promise(function(resolve, reject) {
someAsycOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
});
About then
Each promise has a then method, which takes, as arguments, two functions (then-callbacks).
The first, it is also called onFulfilled callback, will be executed in case of successful completion of the task, and the second, it is also called onRejected callback, in case of failure of the task.
But until we inform the promise of the status of the task, none of these two functions will be called.
The then method returns another promise, which can also be resolved or rejected, and which can also be hung then.
And so in a circle ...
If, during a call to resolve or reject, you pass an argument, then this argument will be forwarded to the next then-callback, which will be executed after the completion of the current task.
var p = new Promise(function(resolve, reject) {
someAsyncOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
}).then(function(value) {
//on success
....
}, function(e) {
//on fail
console.error(e);
});
Then-callback can also throw some value into the next then-callback, simply returning it using the return statement;
var p = new Promise(function(resolve, reject) {
someAsyncOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
}).then(function(value) {
//on success
...
return 'some value';
}, function(e) {
//on fail
console.error(e);
}).then(function(value) {
//value === 'some value' в случае успешного выполнения асинхронной операции
});
About the state
Inside the start function, we have only 3 ways to report the result of the task.
Successful:
- call resolve
Failed:
- call reject
- throw an exception
There is a small rule for functions inside then:
In other words: if then-callback throws an exception or returns another promise that is in the rejected state, then the whole promise will go into rejected status, in all other cases, the promise will be in resolved status (even if then-callback returns nothing).
Look at the then-callback rule, and then the last example ...
It turns out that if the asynchronous operation is successful, then onFulfilled callback will be executed in the first then, and then onFulfilled callback in the second then.
But what if an asynchronous operation fails? An onRejected callback will be executed in the first then, and then ( attention! ) OnFulfilled callback in the second then.
Why? See above rule for then-callback.
Proceeding from it, in order to call the next onRejected callback (which by the way is absent), it is necessary: either return a promise that will be rejected, or throw an exception.
By the way, if it somehow happened that you only need to hang on promise the onRejected callback, that is, the shorthand catch () method
var p = new Promise(function(resolve, reject) {
console.log('Начало асинхронной операции');
someAsyncOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
}).catch(function(e) {
console.log('Асинхронной операция провалена');
console.error(e);
}).then(function() {
console.log('Асинхронная операция завершена!');
});
Or, if you use then, you can just pass null instead of onFulfilled-callback.
But back to the topic ... look what happens ...
onRejected callback in the first then plays the role of a sort of fallback action. Then, the then chain executes further, as if the asynchronous operation was successful.
var p = new Promise(function(resolve, reject) {
console.log('Начало асинхронной операции');
someAsyncOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
}).then(function(result) {
console.log('Асинхронная операция выполнена успешно');
}, function(e) {
console.log('Асинхронная операция провалена');
console.error(e);
}).then(function() {
console.log('Асинхронная операция завершена!');
});
It turns out that promises in JS already out of the box support fallback actions.
And now, as promised, I give a link to the picture preloader , which implements the ability to replace broken pictures with pictures by default.
The principle of its work I just described.
By the way, the preloader uses an allSettled function, the operation of which is based on the same principle of fallback actions.
I also recommend that you familiarize yourself with the code .
Thanks for attention!
PS do not be lazy - read the documentation on promises !