Что такое promise?

Автор: admin

Дата: 13.09.2020 04:19

Как работают Promise

Promise - это объект, который может быть возвращен синхронно из асинхронной функции. Он может быть в одном из трех возможных состояний:

  • если Promise успешно выполнен будет вызван onFulfilled()
  • если Promise выполнен с ошибкой будет вызван onRejected()
  • Pending: еще не выполнено или отклонено

Promise считается выполненным, если он был выполнен или отклонен. Повторный вызов метода resolve() или reject() не даст никакого эффекта.

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

Вот функция, которая возвращает Promise, которая выполнится после заданной задержки:

const wait = time => new Promise((resolve) => setTimeout(resolve, time));
wait(3000).then(() => console.log('Hello!')); // 'Hello!'

Вызов wait(3000) будет ждать 3000 мс(3 секунды), а затем выведет «Hello!». Все совместимые со спецификациями обещания определяют метод .then(), который вы используете для передачи обработчиков и могут принимать значения resolved или rejected.

Конструктор Promise ES6 принимает функцию в качестве аргумента. Эта функция может принимать два параметра: resolve() и reject(). В приведенном выше передается только resolve(). Затем мы вызываем setTimeout(), чтобы создать задержку, а потом будет вызвана функция resolve(). Можно вызвать resolve() или reject() со значениями, которые будут переданы функциям обратного вызова, прикрепленным с помощью .then().

Важные особенности Promises

Стандарт для Promises был определен сообществом спецификаций Promises / A +. Есть много реализаций, которые соответствуют стандарту, включая стандартные Promises ECMAScript JavaScript. Promises, следующие спецификации, должны соответствовать определенному набору правил: Promise - это объект, который предоставляет совместимый со стандартом метод .then(). Promise может перейти в состояние "выполнено" или "отклонено". Выполненное или отклоненное Promise считается выполненным и не должно переходить ни в какое другое состояние. Как только Promise выполнен, он должно иметь значение, которое не должно изменяться. Promise должeн предоставлять метод .then() со следующей подписью:

promise.then(
  onFulfilled: Function,
  onRejected: Function
) => Promise

И onFulfilled(), и onRejected() не являются обязательными, если onFulfilled(), и onRejected() не являются функциями, их следует игнорировать. onFulfilled() будет вызываться после выполнения Promise со значением Promise в качестве первого аргумента. onRejected() будет вызываться после отклонения Promise с указанием причины отклонения в первом аргументе.

onFulfilled() и onRejected() не могут вызываться более одного раза. .then() может вызываться много раз для одного и того же Promise. Другими словами, Promise можно использовать для агрегирования обратных вызовов. .then() должен вернуть новое Promise, Promise2. Если onFulfilled() или onRejected() возвращают значение x, а x является Promiseм, Promise2 будет заблокирован с тем же состоянием и значением, что и x. В противном случае Promise2 будет выполнено со значением x. Если onFulfilled или onRejected генерирует исключение, Promise2 должен быть отклонен в качестве причины. Если onFulfilled не является функцией и Promise1 выполнено, Promise2 должен выполняться с тем же значением, что и Promise1. Если onRejected не является функцией и Promise1 отклонен, Promise2 должно быть отклонен по той же причине, что и Promise1.

Цепочка Promise

Поскольку .then() всегда возвращает новое Promise, можно связать Promise с точным контролем над тем, как и где обрабатывать ошибки. Promise позволяют имитировать поведение try / catch обычного синхронного кода. Как и синхронный код, цепочка приведет к последовательности, которая выполняется последовательно. Другими словами, вы можете:

fetch(url)
  .then(process)
  .then(save)
  .catch(handleErrors)
;

Предполагая, что каждая из функций, fetch(), process() и save(), возвращают Promise, process() будет ждать завершения fetch() перед запуском, а save() будет ждать завершения process() перед запуском. handleErrors() будет запущен только в случае отклонения любого из предыдущих Promise. Вот пример сложной цепочки Promise с несколькими отклонениями:

const wait = time => new Promise(
  res => setTimeout(() => res(), time)
);

wait(200)
  // onFulfilled() возвращает новый Promise
  .then(() => new Promise(res => res('foo')))
  // следующее обещание примет состояние `x`
  .then(a => a)
  // Выше мы вернули развернутое значение `x`
  // так `.then()` выше возвращает выполненное обещание
  // с этим значением:
  .then(b => console.log(b)) // 'foo'
  // null - допустимое значение promise
  .then(() => null)
  .then(c => console.log(c)) // null
  // следующей ошибке пока не сообщается:
  .then(() => {throw new Error('foo');})
  // Вместо этого возвращенное обещание отклоняется
  // с ошибкой в качестве причины:
  .then(
    // Здесь ничего не регистрируется из-за ошибки выше:
    d => console.log(`d: ${ d }`),
    // Теперь обрабатываем ошибку
    e => console.log(e)) // [Error: foo]
  // После обработки предыдущего исключения мы можем продолжить:
  .then(f => console.log(`f: ${ f }`)) // f: undefined
  // Следующее не регистрируется уже обработано,
  //поэтому этот обработчик не вызывается:
  .catch(e => console.log(e))
  .then(() => { throw new Error('bar'); })
  // Когда обещание отклоняется, обработчики успеха пропускаются.
  // Здесь ничего не регистрируется из-за исключения bar:
  .then(g => console.log(`g: ${ g }`))
  .catch(h => console.log(h)) // [Error: bar]
;

Обработка ошибок

Promise есть и успешное выполнение, и обработка ошибок, очень часто можно увидеть код, который делает это:

save().then(
  handleSuccess,
  handleError
);

Но что произойдет, если handleSuccess() выдаст error? Promise, возвращенное из .then(), будет отклонено, но там нет ничего, что могло бы отразить отказ - это означает, что error в вашем приложении будет проглочен.

По этой причине некоторые люди считают приведенный выше код анти-шаблоном и вместо этого рекомендуют следующее:

save()
  .then(handleSuccess)
  .catch(handleError)
;

Разница небольшая, но важная. В первом примере будет обнаружена ошибка, возникающая в операции save(), но ошибка, возникшая в функции handleSuccess(), будет поглощена. Без .catch() ошибка в handleSuccess не обнаруживается. Во втором примере .catch() будет обрабатывать отклонения либо от save(), либо от handleSuccess().

С помощью .catch() обрабатываются оба источника ошибок. Конечно, ошибка save() может быть сетевой ошибкой, тогда как ошибка handleSuccess() может быть вызвана тем, что разработчик забыл обработать определенный код состояния. Что, если вы хотите обращаться с ними по-другому? Вы можете решить их обе:

save()
  .then(
    handleSuccess,
    handleNetworkError
  )
  .catch(handleProgrammerError)
;

Все цепочки Promise лучше заканчивать с помощью .catch().

Как мне отменить Promise?

Одна из первых вещей, о которой часто думают пользователи нового Promise, - это как отменить Promise. Идея: просто отклоните Promise, указав в качестве причины "Cancel". Если вам нужно справиться с ней иначе, чем с «нормальной» ошибкой, выполните ветвление в обработчике ошибок. Вот некоторые типичные ошибки, которые совершают люди при отмене собственного Promise: Добавление .cancel() к обещанию Добавление .cancel() делает Promise нестандартным, но также нарушает другое правило Promise: только функция, создающая Promise, должна иметь возможность разрешить, отклонить или отменить Promise. Его раскрытие нарушает эту инкапсуляцию и побуждает людей писать код, который манипулирует Promiseм в местах, которые не должны о нем знать.

Некоторые умные люди догадались, что есть способ использовать Promise.race() в качестве механизма отмены. Проблема заключается в том, что управление отменой берется из функции, которая создает Promise, что является единственным местом, где вы можете проводить надлежащие действия по очистке, такие как очистка тайм-аутов или освобождение памяти путем очистки ссылок на данные и т. д. Забыть обработать отклоненное Promise отмены Знаете ли вы, что Chrome выдает предупреждающие сообщения по всей консоли, когда вы забываете обработать отклонение Promise? Ой! В отозванном предложении TC39 об отмене предлагался отдельный канал обмена сообщениями для отмены. Он также использовал новую концепцию, называемую токеном отмены. На мой взгляд, это решение значительно раздуло бы спецификацию Promise, и единственная особенность, которую он обеспечило бы, которая напрямую не поддерживается спекуляциями, - это разделение отказов и отмен, что, IMO, не обязательно с самого начала. Вы хотите сделать переключение в зависимости от того, есть ли исключение или отмена? Да, конечно. Это работа Promise? На мой взгляд, нет.

Как правило, я передаю всю информацию, необходимую для Promise, чтобы определить, как allow / deny / cancel во время создания Promise. Таким образом, для Promise не нужен метод .cancel(). Вам может быть интересно, как вы можете узнать, собираетесь ли вы отменить во время создания Promise. «Если я еще не знаю, отменять или нет, как я узнаю, что передать при создании Promise?» Если бы только существовал какой-то объект, который мог бы заменить потенциальную ценность в будущем… о, подождите. Значение, которое мы передаем, чтобы указать, отменять или нет, может быть само Promise. Вот как это может выглядеть:

Отменяемое ожидание - попробуйте на CodePen Мы используем назначение параметров по умолчанию, чтобы запретить отмену по умолчанию. Это делает параметр отмены необязательным. Затем мы устанавливаем timeout, как и раньше, но на этот раз мы фиксируем идентификатор timeout, чтобы мы могли очистить его позже. Мы используем метод cancel.then() для обработки отмены и очистки ресурсов. Это будет выполняться только в том случае, если Promise будет отменено до того, как у него появится возможность разрешиться. Если вы отмените слишком поздно, вы упустили свой шанс. Этот поезд ушел со станции. Примечание. Вам может быть интересно, для чего предназначена функция noop(). Слово noop означает no-op, что означает функцию, которая ничего не делает. Без него V8 выдаст предупреждения: UnhandledPromiseRejectionWarning: необработанное отклонение Promise. Рекомендуется всегда обрабатывать отклонения Promise, даже если вашим обработчиком является noop().