Всем доброго времени суток!
Началось всё с того, что в качестве тестового задания на собеседованиях, я начал просить соискателей реализовать предзагрузчик картинок на JS. Помимо самой предзагрузки, скрипт должен был уметь подставлять fallback-картинку, если нужная картинка не могла быть загружена. Обязательным условием было — использование ES6 Promise.
Затем я подумал: «А почему бы самому не реализовать такой предзагрузчик и не выложить в общее пользование? Да это же еще и отличный повод написать статью на Хабр!».
Собственно, под катом описание логики работы такого предзагрузчика + ссылка на сам предзагрузчик.
Для начала, давайте вспомним — что такое промис в JS.
Про промисы
Промис — это, в первую очередь, способ организации асинхронного кода. Хотя и не обязательно асинхронного…
Для создания промиса необходима функция, которая будет выполнена сразу же, после создания промиса.
Наша задача, внутри этой функции, вызвать один из двух методов, передаваемых в эту функцию автоматически — resolve или reject.
Вызовом одного из этих методов, мы говорим промису о статусе задачи: выполнена успешно (resolve) или неудачно (reject).
Делается это для того, чтобы можно было построить цепочку дальнейших действий в случае успешного или не успешного выполнения задачи.
Про then
У каждого промиса есть метод then, который принимает, в качестве аргументов, две функции (then-callbacks).
Первая, ее еще называют onFulfilled callback — будет выполнена в случае успешного выполнения задачи, а вторая, ее еще называют onRejected callback — в случае провала задачи.
Но пока мы не сообщим промису о статусе задачи, ни одна этих двух функций вызвана не будет.
Метод then возвращает другой промис, который так же может быть resolved или rejected и на который тоже можно повесить then.
И так по кругу…
Если, при вызове resolve или reject, передать какой-то аргумент, то этот аргумент будет проброшен в следующий then-callback, который будет выполнен после завершения текущей задачи.
Then-callback так же может пробросить какое-то значение в следующий then-callback, просто вернув его при помощи оператора return;
Про состояния
Внутри стартовой функции, у нас есть всего 3 способа сообщить о результате задачи.
Выполнено успешно:
Выполнено неудачно:
Для функций внутри then есть небольшое правило:
Другими словами: если then-callback выбрасывает исключение или возвращает другой промис, который будет в состоянии rejected, то и весь промис перейдет в статус rejected, во всех остальных случаях, промис будет в статусе resolved (даже если then-callback ничего не возвращает).
Посмотрите на правило для then-callback, а потом на последний пример…
Получается, что, если асинхронная операция будет выполнена успешно, то выполнится onFulfilled callback в первом then, а далее onFulfilled callback во втором then.
Но что, если асинхронная операция завершится неудачей? Выполнится onRejected callback в первом then, а затем(внимание!) onFulfilled callback во втором then.
Почему? Смотрите выше правило для then-callback.
Исходя из него — чтобы вызвать следующий onRejected callback(которого кстати нет), необходимо: либо вернуть промис, который будет rejected, либо выбросить исключение.
Кстати, если как-то так получилось, что вам нужно повесить на промис только onRejected callback, то есть shorthand-метод catch()
Либо, если используете then, вместо onFulfilled-callback можно просто передать null.
Но вернемся к теме… посмотрите что получается…
onRejected callback в первом then играет роль эдакого fallback-действия. Затем, цепочка then выполняется дальше, как если бы асинхронная операция была выполнена успешно.
Получается, что промисы в JS уже «из коробки» поддерживают fallback-действия.
А теперь, как и обещал, даю ссылку на предзагрузчик картинок, в котором реализована возможность подмены битых картинки на картинки по умолчанию.
Принцип его работы я только что описал.
Кстати, предзагрузчик использует allSettled-функцию, работа которой основывается на том же принципе fallback-действий.
Так же рекомендую ознакомиться с кодом.
Спасибо за внимание!
P.S. не поленитесь — почитайте документацию по промисам!
Началось всё с того, что в качестве тестового задания на собеседованиях, я начал просить соискателей реализовать предзагрузчик картинок на JS. Помимо самой предзагрузки, скрипт должен был уметь подставлять fallback-картинку, если нужная картинка не могла быть загружена. Обязательным условием было — использование ES6 Promise.
Затем я подумал: «А почему бы самому не реализовать такой предзагрузчик и не выложить в общее пользование? Да это же еще и отличный повод написать статью на Хабр!».
Собственно, под катом описание логики работы такого предзагрузчика + ссылка на сам предзагрузчик.
Для начала, давайте вспомним — что такое промис в JS.
Про промисы
Промис — это, в первую очередь, способ организации асинхронного кода. Хотя и не обязательно асинхронного…
Для создания промиса необходима функция, которая будет выполнена сразу же, после создания промиса.
Наша задача, внутри этой функции, вызвать один из двух методов, передаваемых в эту функцию автоматически — resolve или reject.
Вызовом одного из этих методов, мы говорим промису о статусе задачи: выполнена успешно (resolve) или неудачно (reject).
Делается это для того, чтобы можно было построить цепочку дальнейших действий в случае успешного или не успешного выполнения задачи.
var p = new Promise(function(resolve, reject) {
someAsycOperation(function(e, result) {
if (e) {
reject(e);
} else {
resolve(result);
}
});
});
Про then
У каждого промиса есть метод then, который принимает, в качестве аргументов, две функции (then-callbacks).
Первая, ее еще называют onFulfilled callback — будет выполнена в случае успешного выполнения задачи, а вторая, ее еще называют onRejected callback — в случае провала задачи.
Но пока мы не сообщим промису о статусе задачи, ни одна этих двух функций вызвана не будет.
Метод then возвращает другой промис, который так же может быть resolved или rejected и на который тоже можно повесить then.
И так по кругу…
Если, при вызове resolve или reject, передать какой-то аргумент, то этот аргумент будет проброшен в следующий then-callback, который будет выполнен после завершения текущей задачи.
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 так же может пробросить какое-то значение в следующий then-callback, просто вернув его при помощи оператора return;
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' в случае успешного выполнения асинхронной операции
});
Про состояния
Внутри стартовой функции, у нас есть всего 3 способа сообщить о результате задачи.
Выполнено успешно:
- вызвать resolve
Выполнено неудачно:
- вызвать reject
- выбросить исключение
Для функций внутри then есть небольшое правило:
Другими словами: если then-callback выбрасывает исключение или возвращает другой промис, который будет в состоянии rejected, то и весь промис перейдет в статус rejected, во всех остальных случаях, промис будет в статусе resolved (даже если then-callback ничего не возвращает).
Посмотрите на правило для then-callback, а потом на последний пример…
Получается, что, если асинхронная операция будет выполнена успешно, то выполнится onFulfilled callback в первом then, а далее onFulfilled callback во втором then.
Но что, если асинхронная операция завершится неудачей? Выполнится onRejected callback в первом then, а затем(внимание!) onFulfilled callback во втором then.
Почему? Смотрите выше правило для then-callback.
Исходя из него — чтобы вызвать следующий onRejected callback(которого кстати нет), необходимо: либо вернуть промис, который будет rejected, либо выбросить исключение.
Кстати, если как-то так получилось, что вам нужно повесить на промис только onRejected callback, то есть shorthand-метод catch()
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('Асинхронная операция завершена!');
});
Либо, если используете then, вместо onFulfilled-callback можно просто передать null.
Но вернемся к теме… посмотрите что получается…
onRejected callback в первом then играет роль эдакого fallback-действия. Затем, цепочка then выполняется дальше, как если бы асинхронная операция была выполнена успешно.
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('Асинхронная операция завершена!');
});
Получается, что промисы в JS уже «из коробки» поддерживают fallback-действия.
А теперь, как и обещал, даю ссылку на предзагрузчик картинок, в котором реализована возможность подмены битых картинки на картинки по умолчанию.
Принцип его работы я только что описал.
Кстати, предзагрузчик использует allSettled-функцию, работа которой основывается на том же принципе fallback-действий.
Так же рекомендую ознакомиться с кодом.
Спасибо за внимание!
P.S. не поленитесь — почитайте документацию по промисам!