Promise существует уже несколько лет, и множество библиотек API позволяют пользователям с легкостью выполнять асинхронные операции. Мы все знаем, что можем сделать асинхронный вызов, как показано ниже.
someAsyncCall.then(res => res.data).then();
Но я уверен, что некоторые новички не знают, как работает Promise и что им нужно учитывать при использовании или построении собственного обещания.
Если вы среди них, не торопитесь, чтобы прочитать. Я расскажу обо всем, что касается Promise, наизнанку.
Я буду ссылаться на успешные и неудачные обратные вызовы, установленные на then
, на onResolved
и onRejected
на протяжении всего написания.
Код ниже будет использован для иллюстрации.
const delJSON = (url) => { return new Promise((resolve, reject) => { setTimeout(function() { if (url) { resolve({ message: 'deleted', id: 2 }); } else { reject({ error: 'delJSON failed' }); } }, 500); }); }; const getJSON = (url) => { return new Promise((resolve, reject) => { setTimeout(function() { if (url) { resolve({ data: [1, 2], id: 1 }); } else { reject({ error: 'getJSON failed' }); } }, 2000); }); };
Основы
Три состояния обещания
Обещание может быть только в одном из следующих трех состояний:
- Ожидание: исходное состояние
- Решено: операция выполнена успешно.
- Отклонено: операция не удалась
Единственный способ, которым Ожидает превратиться в Решено или Отклонено, - это позвонить по телефону resolve(value)
или reject(error)
.
После изменения состояния вы не сможете вернуться к Pending
. Он останется навсегда.
Инициализация обещания
Вызов конструктора Promise для возврата экземпляра.
promise.then(function(data) { // success }, function(error) { // failure });
Конструктор обещания принимает функцию в качестве единственного аргумента, который имеет resolve
и reject
в качестве параметров функции. Вызов resolve
со значением вызовет onResolved
, который устанавливается путем вызова then
в экземпляре Promise. Точно так же вызов reject
с ошибкой вызовет onRejected
.
onRejected
обратный вызов не является обязательным, но настоятельно рекомендуется установить его для обработки ошибок.
Следует иметь в виду, что Promise действует как контейнер, содержащий будущий результат операций. Они выполняются сразу после создания.
let promise = new Promise(function(resolve, reject) { console.log('start'); resolve(); }); promise.then(function() { console.log('done'); }); console.log('ok'); // start // ok // done
потом
Все экземпляры обещаний имеют then
метод, который используется для добавления двух обратных вызовов: onResolved
и onRejected
.
const onResolved = (data) => {return data.id}; const onRejected = (error) => {}; promise.then(onResolved, onRejected).then(id => console.log(id));
Вы можете объединить then
в цепочку, возвращая значение, которое затем переносится в другой новый экземпляр обещания.
Ловить
catch
- это сахарный синтаксис для .then(null, onRejected)
, который запускается при возникновении ошибки.
getJSON().then(data => { }).catch(err => { // {error: 'getJSON failed'} });
Все
Вы можете использовать Promise.all
для выполнения нескольких операций.
const p = Promise.all([p1, p2, p3]);
Обычно Promise.all
принимает массив экземпляров обещаний, который определяет конечное состояние для возвращенного обещания от вызова Promise.all
.
Давайте сначала посмотрим, как мы разрешаем p
.
По сути, для решения p
необходимо решить p1,p2,p3
все.
const p = Promise.all([getJSON('baidu.com'), delJSON('google.com')]); p.then(data => data).catch(err => console.log(err)); // [{data: [1,2], id: 1}, {message: 'deleted', id: 2}]
Как видите, разрешенные значения будут переданы onResolved
в том же порядке, в котором мы создаем все экземпляры обещаний.
А теперь давайте посмотрим на отклоненное дело. Если какой-либо из экземпляров будет отклонен, p
будет отклонен, а причина первого отклоненного обещания будет передана onRejected
.
const p = Promise.all([getJSON('baidu.com'), delJSON()]); p.then(data => console.log(data)).catch(err => console.log(err)); // {error: 'delJSON failed'}
Гонка
Это говорит само за себя - первое resolved
или rejected
обещание будет определять состояние экземпляра обещания, созданного race
.
const p = Promise.race([getJSON('baidu.com'), delJSON('google.com')]); p.then(data => console.log(data)).catch(err => console.log(err)); // { message: 'deleted', id: 2 }
Передовой
Разрешить с помощью другого экземпляра обещания
Я бы назвал этот сценарий вложенным обещанием. Вложенное обещание будет определять состояние внешнего обещания.
getJSON('baidu.com').then(data => { console.log(data); // not called }).catch(err => { console.log('the error ', err); // {error: 'operation failed'} }); const delJSON = (url) => { return new Promise((resolve, reject) => { setTimeout(function() { if (url) { resolve({ message: 'deleted', id: 2 }); } else { reject({ error: 'delJSON failed' }); } }, 500); }); };
В приведенном выше примере вы можете подумать, что будет вызвано then
, но это не так, вместо этого будет запущено catch
, поскольку управление принимает вложенное обещание delJSON
. Другими словами, внутреннее состояние обещания имеет приоритет над внешним.
Обработка ошибок
Вы можете задаться вопросом, какой способ обработки ошибок является предпочтительным - между установкой onRejected
или использованием catch
. Ответ второй - улов. Проблема с onRejected
в том, что если ошибка происходит в onResolved
, onRejected
просто обращает на нее внимание, тогда как catch
ее поймает!
getJSON('baidu.com').then(data => { [].move(); }, (err) => { console.log('operation failed', err); // turn blind eyes to error }); delJSON('google.com').then(data => { [].move(); }).catch(err => { console.log('operation failed', err); // caught! since I see it });
Вы также можете использовать catch
для обнаружения ошибок отдельных операций.
getJSON('baidu.com').then(data => { data.move(); }).catch(err => { console.log('1st error ', err); }).then(data => { data.move(); }).catch(err => { console.log('2nd error ', err); }); // prints both errors
Catch
позволяет эффективно выявлять ошибки в этих местах.
- Аргумент функции конструктора обещаний
onResolved
- функции, возвращающие новый экземпляр обещания в
onResolved
Однако указанная ниже ошибка не будет обнаружена, поскольку обещание уже разрешено, а состояние остается, и никакие операции на него не повлияют.
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('not ok');
});
promise
.then(function(value) { console.log(value) }) // ok
.catch(function(error) { console.log(error) }); // not called
Promise.resolve и Promise.reject
Мы также можем вызвать эти два метода в конструкторе Promise с примитивными значениями.
Promise.resolve('foo');
new Promise(resolve => resolve('foo'));
// above expressions are the same
Promise.reject('bar')
new Promise(reject => reject('bar'));
При выполнении будет создан экземпляр обещания с состоянием resolved
или rejected
.
Promise.all работает последовательно или параллельно?
Это озадачивает многих, и я вижу, что этот вопрос задают пользователи в stackoverflow. Фактически, все экземпляры обещаний уже выполняются к тому моменту, когда мы вызываем с ними Promise.all
.
const p = Promise.all([delJSON('google.com'), getJSON('baidu.com']);
Помните, я описываю обещание как контейнер, который сохраняет будущий результат операций раньше? Promise.all
просто ждет, пока все обещания не будут разрешены, а затем передают разрешенные значения до onResolved
. Таким образом, строго говоря, мы не можем сказать, что Promise.all
работает последовательно или параллельно.
Вот и все, что вам нужно знать Promise
. Для быстрого обзора я перечисляю следующие ключевые моменты.
- Три состояния:
pending
,resolved
,rejected
- Вызов конструктора
Promise
с функцией в качестве единственного аргумента, который принимаетresolve
иreject
два обратных вызова - Для успешной / неудачной операции вызовите
resolve
сdata
илиreject
с ошибкой, которая, в свою очередь, вызоветthen
илиcatch
- Вложенное обещание определяет состояния внешнего обещания
- Для лучшего покрытия ошибок используйте
catch
, а неonRejected
- Вызов
Promise.resolve
илиPromise.reject
с примитивными значениями в основном генерирует новый экземпляр обещания Promise.all
заботится только о состоянии, а не о том, в каком порядке выполняются обещания- Используйте
then().catch().then().catch()
для обработки ошибок в различных операциях
Надеюсь, я помогу вам лучше понять Promise.
Далее я расскажу об использовании Async/Await
в ES7.