Promises в JavaScript

10.01.2020

Если вы хотите совладать с асинхронным кодом в JavaScript, не прибегая к функциям обратного вызова (callback), то вам следует обратиться к Promise'ам. Их можно определить как объект, который выполняет асинхронную операцию. Как только результат или ошибка операциии будут известны, Promise сообщит об этом.

Прошло всего несколько лет как они появились, впервые Promise были стандартизированы и представлены в рамках ES2015, а позже были представлены асинхронные функции в ES2017. Асинхронные функции используют Promise'ы, как строительные блоки. Но нельзя взять и сразу начать использовать асинхронные функции не познакомившись с Promise'ами.

Внутреннее устройство

У Promise'a может быть 3 состояния. Когда вы только создали Promise, он находится в состояние ожидания (Pending). После этого, чтобы его запустить надо присвоить ему обработчики для двух других состояний: выполнено успешно и выполнено с ошибкой. После того как Promise перешлел в одно из двух состояний, он больше не может изменить свое состояние. Обработчики будут вызваны только один раз.

Рассмотрим пример создания Promise.

let promise = new Promise((resolve, reject) => {
  // здесь сосредоточен код, который будет может выполнять любые асинхронные операции
  // как только результат будет получен он должен вызвать функцию resolve(result)
  // если произойдет ошибка то reject(error);      	
});

Чтобы добавить обработчики, следует вызвать функцию then у объекта Promise.

promise.next( result => {}, error => {});
// можно добавить только обработчик на успешное выполнение
promise.next( result => {});
// либо только на ошибку
promise.next( null, error => {});
// можно сократить
promise.catch( error => {})
// 

Приведем полноценный пример работы с Promise, сделаем функцию delay, которая будет попросту ждать 1000 мс.

function delay(period = 1000) {
  return new Promise(resolve => {
    setTimeout(() => resolve(), period); 
  });
}


console.log(new Date());
delay().then(() => {
  // пауза сделанау    
  console.log(new Date());
});

в результате будет выведено следующее в консоль:

2020-01-10T22:52:36.084Z
2020-01-10T22:52:37.088Z

Цепочки Promise

Во время выполнения обработчика результата или ошибки можно вызвать вернуть Promise и использовать снова метод then.

delay(1000)
  .then( () => delay(1000))
  .then( () => delay(1000))
  .then( () => delay(1000))
  .then( () => delay(1000))
  .then( () => delay(1000))
  .catch( () => console.log('error'));

В приведенном примере происходит вызов нашей асинхронной функции delay, после каждого вызова происходит следующий и так всего 5 раз. В результате произойдет задержка на 6 секунд. Как мы уже знаем delay не порождает ошибки, но здесь приведен пример обработки их.

В реальных цепочках Promise'ов могут возникать ошибки в разных местах. Мы можем сразу же обработать их и если ошибка не критичная то обработчик может вернуть Promise и выполнение цепочки продолжится. либо можно выкинуть exception, который попадет в следующий обработчик ошибок в цепочке.

new Promise((resolve, reject) => {
 throw new Error('Error')
})
.catch((err) => { 
 console.log('error1');  
 throw new Error('Error') 
}) 
.catch((err) => { console.error('error2') })

Полезные методы

Если вы хотите выполнить несколько асинхронных операций одновременной, то для этого следуте воспользоваться методом Promise.all.

const req1 = fetch('people.json');
const req2 = fetch('houses.json');

Promise.all([req1, req2]).then(([res1, res2]) => {
    console.log('res1', res1);
    console.log('res2', res2);
});

В результате выполнения Promise.all будут возвращен массив результатов выполнения асинхронных функций. Если хотя бы один из Promise, выкинет ошибку, то результат Promise.all будет ошибка.

Второй метод Promise.race, работает совсем иначе. На вход так же подается массив Promise, но в результате будет возвращен результат только одного Promise'a, который раньше всех перейдет в другое состояние.

const promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(reject, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
}).catch( (error) => console.error('1'+error));

Существует два метода Promise.resolve и Promise.reject. Каждый из них создает который при привязке обработчика сразу переходит в нужное состояние и передает свой аргумент.

Promise.resolve('Hello')
  .then( res => console.log(res))
  .catch( error => console.log('Never mind'));  
Promise.reject("World")
  .then( res => console.log('Never mind'))      
  .catch( error => console.log(error));
#jsbook#javascript

Еженедельная рассылка новостей

Запускаем JavaScript

Асинхронное программирование в JavaScript