Ресурсы: Promise#1 Promise#2 Наглядный пример промисов
Промисы - дословно "
обещания", технология, без которой фронтенд сейчас невозмоно представить.
Данная технология повзоляет комфортно работать с различными асинхронными операциями.
Разбираем на примере:
Код
'use strict';
console.log('Запрос данных...');
Этот код синхронный и он выполняется сразу же.
Дальше прописываем асинхронный код, представим, что в переменной
product у нас какие-то данные, полученные от сервера и мы с ними тоже что-то делаем:
Код
'use strict';
console.log('Запрос данных...');
setTimeout(() => {
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
}, 2000);
Мы видим, что у нас появляется так называемое "
дерево каллбэков", и их может быть гораздо больше.
Вместо
setTimeout у нас могут быть и другие действия, например, работа с сервером.
Если запустить код, написанный выше, то в консоли мы получим:
Цитата
Запрос данных...
Подготовка данных...
{ name: 'TV', price: 2000, status: 'order' }
Код асинхронный, но благодаря каллбэкам у нас соблюдается правильная очерёдность вывода данных.
Но чтобы наш код не разрастался, не превращался в ад обратных вызовов (
callback hell), используют
промисы.
Промисы
Сначала создаётся новый промис:
Код
const req = new Promise();
Во внутрь помещается каллбэк-функция, которая принимает два аргумента
resolve и
reject Код
const req = new Promise(function(resolve, reject) {
});
Возьмём наш код, написанный ранее, и поместим его внутрь промиса:
Код
'use strict';
console.log('Запрос данных...');
const req = new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
}, 2000);
});
Два аргумента
resolve и
reject обозначают функции, которые в будущем мы можем сами передавать.
Если по-простому, то
resolve это значит что что-то выполнилось правильно, обещание выполнилось, а
reject наоборот.
В нашем скрипте вот эта часть кода:
Код
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
будет выполнена только в том случае, если выполнится без ошибок код выше:
Код
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
Следовательно, мы можем заменить вот этот код на функцию
resolve:
Код
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
Код примет вид:
Код
'use strict';
console.log('Запрос данных...');
const req = new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
resolve();
}, 2000);
});
Чтобы использовать созданный промис, существуют два метода
then и
catch.
Метод then выполняется на промисе в случае положительного исхода. Функция
resolve передаётся в промис как аргумент, и вот как раз как аргумент мы и помещаем её в метод
then Код
req.then(() => {
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
});
В данном случае внутри этой каллбэк-функции
product будет недоступен, поэтому его нужно заретёрнить(вернуть) из
req.
Мы можем это сделать в вызове функции
resolve, передав ей
product как аргумент:
Код
'use strict';
console.log('Запрос данных...');
const req = new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
resolve(product);
}, 2000);
});
Цитата
Как понял этот момент я: Метод
then, который вызывается на промисе в случае положительного его результата, принимает в себя как аргумент функцию, которая передаётся в аргумент
resolve промиса.
То есть, фактически получаем вместо
resolve(product) будет внутри промиса вызываться переданная функция с аргументом
product, грубо говоря внутри метода
then лежит та самая функция
resolve, которая вызывается в промисе в случае положительного его исхода.
Чтобы не путаться, в методе вместо
product можно назвать аргумент
data, можно назвать как угодно
Код
req.then((data) => {
setTimeout(() => {
data.status = 'order';
console.log(data);
}, 2000);
});
При вызове
resolve(product) в аргумент
data метода
then передаётся
product и используется внутри кода этой функции
Получается такая цепочка:
1. Методы
then и
catch описывают варианты развития событий для положительного и отрицательного завершения промиса.
Эти методы в качестве своих аргументов передают в промис функции, которые попадают в аргументы
resolve и
reject соответственно.
2. При положительном завершении промиса вызывается функция, переданная в аргумент
resolve, при отрицательном - функция, переданная в аргумент
reject. 3. В нашем случае промис завершился положительно и мы вызываем функцию из аргумента
resolve, а в нём у нас находится функция, которую передал метод
then:
Код
(data) => {
setTimeout(() => {
data.status = 'order';
console.log(data);
}, 2000);
}
То есть, в данном случае
resolve и код выше равнозначны, поэтому вызывая
resolve с аргументом
product, мы этот аргумент увидим и в коде переданной функции, то есть по факту будет так:
Код
(product) => {
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
}
Допустим, что после выполнения этого кода:
Код
setTimeout(() => {
product.status = 'order';
console.log(product);
}, 2000);
нам понадобится выполнить еще какие-то действия.
И тогда нам эту конструкцию потребуется также обернуть в промис:
Код
'use strict';
console.log('Запрос данных...');
const req = new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('Подготовка данных...');
const product = {
name: 'TV',
price: 2000
}
resolve(product);
}, 2000);
});
req.then((product) => {
const req2 = new Promise((resolve, reject) => {
setTimeout(() => {
product.status = 'order';
resolve(product);
}, 2000);
});
req2.then(data => {
console.log(data);
});
});
Огромное преимущество перед каллбэками у промисов в том, что мы можем возвращать промис из
then по цепочке.
В коде выше мы можем убрать создание новой переменной
req2 и вместо этого просто возвращать новый промис:
Код
req.then((product) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
product.status = 'order';
resolve(product);
}, 2000);
});
}).then(data => {
console.log(data);
});
Так как мы возвращаем промис, то мы опять его можем обработать при помощи метода
then, поэтому можно использовать цепочку из
then.
Кроме того, мы можем возвращать не только промисы, мы можем, к примеру, модифицировать полученные данные и далее через
then с ними что-то делать, например, добавить новое свойство в наш
product:
Код
req.then((product) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
product.status = 'order';
resolve(product);
}, 2000);
});
}).then(data => {
data.modify = true;
return data;
}).then(data => {
console.log(data);
});
Что получаем в консоли:
Цитата
Запрос данных...
Подготовка данных...
{ name: 'TV', price: 2000, status: 'order', modify: true }
Цитата
"В промисах есть два варианта использования return:
1. используется для возврата значения из промиса.
В асинхронных запросах на колбеках передаются функции-колбеки, в которые передается результат. В промисах же колбека нет. Чтобы использовать результат асинхронной функции в промисах, возвращается (return) нужный результат. Этот возвращаемый результат попадет в следующую цепочку then
2. второй случай — это когда возвращается сам промис.
Возвращать промис нужно, чтобы следующая цепочка then могла выполнить этот промис и получить его результат."
Второй аргумент промиса, который называется
reject, это тоже функция, которая выполняется, когда наш промис завершился неудачно.
Для его обработки существует метод
catch, который ставится в конце цепочки.
Заменим в нашем коде вызов функции
resolve на
reject и добавим обработку с помощью метода
catch Код
req.then((product) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
product.status = 'order';
reject();
}, 2000);
});
}).then(data => {
data.modify = true;
return data;
}).then(data => {
console.log(data);
}).catch(() => {
console.error('Произошла ошибка');
});
Результат в консоли:
Цитата
Запрос данных...
Подготовка данных...
Произошла ошибка
Помимо
then и
catch есть еще блок кода, который называется
finally, он всегда используется в самом конце после всех взаимодействий и обработок ошибок.
Данный блок позволяет нам выполнить действия при абсолютно любом исходе промиса, то есть действия, которые должны быть произведены абсолютно всегда.
Код
req.then((product) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
product.status = 'order';
resolve(product);
}, 2000);
});
}).then(data => {
data.modify = true;
return data;
}).then(data => {
console.log(data);
}).catch(() => {
console.error('Произошла ошибка');
}).finally(() => {
console.log('Finally');
});
Как вариант, в блок
finally, к примеру, можно вставить код очистки формы после её отправки, ведь не важно, успешно отправились данные формы или с ошибкой, очистить форму после отправки нам нужно в любом случае.
Кроме того,в промисах есть ещё два метода, которые называются
all и
race Создадим переменную
test, в которой у нас будет функция, содержащая аргумент
time, и эта функция будет возвращать нам новый промис
Код
const test = time => {
return new Promise(resolve => {
setTimeout(() => resolve(), time);
});
};
test(1000).then(() => {
console.log('1000 ms');
});
test(2000).then(() => {
console.log('2000 ms');
});
То есть в аргумент функции будет приходить какое-то количество времени, и потом через это количество времени будет запускаться функция
resolve (в данной функции обойдёмся без аргумента
reject).
В
then мы помещаем функцию, которая будет нашим резолвом и будет просто выводить в консоль строку "
1000 ms" и "
2000 ms"
Возвращаясь к методу
all: он принимает в себя массив промисов и возвращает новый промис, который завершится, когда завершатся все промисы в массиве:
Код
Promise.all([test(1000), test(2000)]).then(() => {
console.log('Все промисы выполнены');
});
Команда
Promise.all нам позволяет убедиться, что все наши промисы уже выполнились. Например, нам приходят изображения от разных серверов, и мы хотим, чтобы они были показаны все сразу, то есть мы должны подождать загрузки всех наших промисов и только потом что-то делать.
Promise.all ждёт завершения всех промисов, переданных в массив, и только потом будет что-то выполнять.
Метод
race по синтаксису похож, но он позволяет определить когда какой-то из промисов выполнится первым и после этого выполняет какую-то определённую операцию дальше.
Код
Promise.race([test(1000), test(2000)]).then(() => {
console.log('Эта надпись появится через 1 секунду');
});
Добавлять комментарии могут только зарегистрированные пользователи.