Перевод «promise» на русский
Но сейчас многие сомневаются в том, что это обещание удастся сдержать.
Each person joining scouts makes a promise.
Каждый человек, вступая в скауты, дает обещание.
Note that some financial institutions do not deliver what they promise.
Кроме того, некоторые финансовые учреждения только и занимаются тем, что дают обещания.
Please remember that this «promise» is within YOU.
Пожалуйста, помните, что это «обещание» находится внутри ВАС.
Google has kept that promise too.
И до сих пор Google сдерживала свое обещание.
He was certainly motivated by this NATO promise.
Он, безусловно, был мотивирован обещанием со стороны НАТО.
But the promise is the promise until both sides agree to change it.
Но обещание остается обещанием, пока обе стороны не согласятся его изменить.
Even believing in no promise is a promise of some kind.
Даже вера в отсутствие обещаний — это уже своего рода обещание.
The promise of the revolution was the promise that the people’s voice would be heard.
Обещанием революции было обещание, что голос народа будет услышан.
A promise isn’t broken if both parties agree to changing the promise.
Но обещание остается обещанием, пока обе стороны не согласятся его изменить.
But the promise remained only a promise.
Но обещание так и осталось лишь обещанием.
I always remember the saying. a promise is a promise.
Я всегда придерживаться правила: Обещание это обещание.
The promise that will come will be a false promise.
Обещание, которое придет, окажется ложным обещанием.
But the promise of power is a heady promise.
Но обещание руководства осталось лишь обещанием.
A promise is a promise, Lieutenant Dan.
Обещание есть обещание, лейтенант Дэн.
We promise our clients two things.
В связи с этим, мы настоятельно рекомендуем нашим клиентам две вещи.
Kyrgyz officials promise they would never obstruct passage.
Кыргызские чиновники обещают, что никогда не будут препятствовать прохождению [по дороге людей].
They deliver what others only promise.
Ведь он всего лишь осуществил то, о чём другие только мечтают.
He has apparently forgotten this promise.
И, судя по всему, не забыли об этом обещании.
I cannot promise that forgiving is easy.
Я не могу утверждать, что это понимание поддерживать в себе очень легко.
Возможно неприемлемое содержание
Примеры предназначены только для помощи в переводе искомых слов и выражений в различных контекстах. Мы не выбираем и не утверждаем примеры, и они могут содержать неприемлемые слова или идеи. Пожалуйста, сообщайте нам о примерах, которые, на Ваш взгляд, необходимо исправить или удалить. Грубые или разговорные переводы обычно отмечены красным или оранжевым цветом.
Зарегистрируйтесь, чтобы увидеть больше примеров. Это просто и бесплатно
Ничего не найдено для этого значения.
Promise
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/promise-basics.
Promise (обычно их так и называют «промисы») – предоставляют удобный способ организации асинхронного кода.
В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже.
Что такое Promise?
Promise – это специальный объект, который содержит своё состояние. Вначале pending («ожидание»), затем – одно из: fulfilled («выполнено успешно») или rejected («выполнено с ошибкой»).

На promise можно навешивать колбэки двух типов:
- onFulfilled – срабатывают, когда promise в состоянии «выполнен успешно».
- onRejected – срабатывают, когда promise в состоянии «выполнен с ошибкой».
Способ использования, в общих чертах, такой:
- Код, которому надо сделать что-то асинхронно, создаёт объект promise и возвращает его.
- Внешний код, получив promise , навешивает на него обработчики.
- По завершении процесса асинхронный код переводит promise в состояние fulfilled (с результатом) или rejected (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.
Синтаксис создания Promise :
var promise = new Promise(function(resolve, reject) < // Эта функция будет вызвана автоматически // В ней можно делать любые асинхронные операции, // А когда они завершатся — нужно вызвать одно из: // resolve(результат) при успешном выполнении // reject(ошибка) при ошибке >)
Универсальный метод для навешивания обработчиков:
promise.then(onFulfilled, onRejected)
- onFulfilled – функция, которая будет вызвана с результатом при resolve .
- onRejected – функция, которая будет вызвана с ошибкой при reject .
С его помощью можно назначить как оба обработчика сразу, так и только один:
// onFulfilled сработает при успешном выполнении promise.then(onFulfilled) // onRejected сработает при ошибке promise.then(null, onRejected)
Для того, чтобы поставить обработчик только на ошибку, вместо .then(null, onRejected) можно написать .catch(onRejected) – это то же самое.
Синхронный throw – то же самое, что reject
Если в функции промиса происходит синхронный throw (или иная ошибка), то вызывается reject :
'use strict'; let p = new Promise((resolve, reject) => < // то же что reject(new Error("o_O")) throw new Error("o_O"); >) p.catch(alert); // Error: o_O
Посмотрим, как это выглядит вместе, на простом примере.
Пример с setTimeout
Возьмём setTimeout в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом «result»:
'use strict'; // Создаётся объект promise let promise = new Promise((resolve, reject) => < setTimeout(() =>< // переведёт промис в состояние fulfilled с результатом "result" resolve("result"); >, 1000); >); // promise.then навешивает обработчики на успешный результат или ошибку promise .then( result => < // первая функция-обработчик - запустится при вызове resolve alert("Fulfilled: " + result); // result - аргумент resolve >, error => < // вторая функция - запустится при вызове reject alert("Rejected: " + error); // error - аргумент reject >);
В результате запуска кода выше – через 1 секунду выведется «Fulfilled: result».
А если бы вместо resolve(«result») был вызов reject(«error») , то вывелось бы «Rejected: error». Впрочем, как правило, если при выполнении возникла проблема, то reject вызывают не со строкой, а с объектом ошибки типа new Error :
// Этот promise завершится с ошибкой через 1 секунду var promise = new Promise((resolve, reject) => < setTimeout(() =>< reject(new Error("время вышло!")); >, 1000); >); promise .then( result => alert("Fulfilled: " + result), error => alert("Rejected: " + error.message) // Rejected: время вышло! );
Конечно, вместо setTimeout внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал resolve или reject , которые передадут результат обработчикам.
Только один аргумент
Функции resolve/reject принимают ровно один аргумент – результат/ошибку.
Именно он передаётся обработчикам в .then , как можно видеть в примерах выше.
Promise после reject/resolve – неизменны
Заметим, что после вызова resolve/reject промис уже не может «передумать».
Когда промис переходит в состояние «выполнен» – с результатом (resolve) или ошибкой (reject) – это навсегда.
'use strict'; let promise = new Promise((resolve, reject) => < // через 1 секунду готов результат: result setTimeout(() =>resolve("result"), 1000); // через 2 секунды — reject с ошибкой, он будет проигнорирован setTimeout(() => reject(new Error("ignored")), 2000); >); promise .then( result => alert("Fulfilled: " + result), // сработает error => alert("Rejected: " + error) // не сработает );
В результате вызова этого кода сработает только первый обработчик then , так как после вызова resolve промис уже получил состояние (с результатом), и в дальнейшем его уже ничто не изменит.
Последующие вызовы resolve/reject будут просто проигнорированы.
А так – наоборот, ошибка будет раньше:
'use strict'; let promise = new Promise((resolve, reject) => < // reject вызван раньше, resolve будет проигнорирован setTimeout(() =>reject(new Error("error")), 1000); setTimeout(() => resolve("ignored"), 2000); >); promise .then( result => alert("Fulfilled: " + result), // не сработает error => alert("Rejected: " + error) // сработает );
Промисификация
Промисификация – это когда берут асинхронную функциональность и делают для неё обёртку, возвращающую промис.
После промисификации использование функциональности зачастую становится гораздо удобнее.
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
Функция httpGet(url) будет возвращать промис, который при успешной загрузке данных с url будет переходить в fulfilled с этими данными, а при ошибке – в rejected с информацией об ошибке:
function httpGet(url) < return new Promise(function(resolve, reject) < var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function() < if (this.status == 200) < resolve(this.response); >else < var error = new Error(this.statusText); error.code = this.status; reject(error); >>; xhr.onerror = function() < reject(new Error("Network Error")); >; xhr.send(); >); >
Как видно, внутри функции объект XMLHttpRequest создаётся и отсылается как обычно, при onload/onerror вызываются, соответственно, resolve (при статусе 200) или reject .
httpGet("/article/promise/user.json") .then( response => alert(`Fulfilled: $`), error => alert(`Rejected: $`) );
Метод fetch
Заметим, что ряд современных браузеров уже поддерживает fetch – новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем httpGet . И – да, этот метод использует промисы. Полифил для него доступен на https://github.com/github/fetch.
Цепочки промисов
«Чейнинг» (chaining), то есть возможность строить асинхронные цепочки из промисов – пожалуй, основная причина, из-за которой существуют и активно используются промисы.
Например, мы хотим по очереди:
- Загрузить данные посетителя с сервера (асинхронно).
- Затем отправить запрос о нём на github (асинхронно).
- Когда это будет готово, вывести его github-аватар на экран (асинхронно).
- …И сделать код расширяемым, чтобы цепочку можно было легко продолжить.
Вот код для этого, использующий функцию httpGet , описанную выше:
'use strict'; // сделать запрос httpGet('/article/promise/user.json') // 1. Получить данные о пользователе в JSON и передать дальше .then(response => < console.log(response); let user = JSON.parse(response); return user; >) // 2. Получить информацию с github .then(user => < console.log(user); return httpGet(`https://api.github.com/users/$`); >) // 3. Вывести аватар на 3 секунды (можно с анимацией) .then(githubUser => < console.log(githubUser); githubUser = JSON.parse(githubUser); let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); setTimeout(() =>img.remove(), 3000); // (*) >);
Самое главное в этом коде – последовательность вызовов:
httpGet(. ) .then(. ) .then(. ) .then(. )
При чейнинге, то есть последовательных вызовах .then…then…then , в каждый следующий then переходит результат от предыдущего. Вызовы console.log оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.
Если очередной then вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.
- Функция в первом then возвращает «обычное» значение user . Это значит, что then возвратит промис в состоянии «выполнен» с user в качестве результата. Он станет аргументом в следующем then .
- Функция во втором then возвращает промис (результат нового вызова httpGet ). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий then с его результатом.
- Третий then ничего не возвращает.
Схематично его работу можно изобразить так:

Значком «песочные часы» помечены периоды ожидания, которых всего два: в исходном httpGet и в подвызове далее по цепочке.
Если then возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки будет ждать.
То есть, логика довольно проста:
- В каждом then мы получаем текущий результат работы.
- Можно его обработать синхронно и вернуть результат (например, применить JSON.parse ). Или же, если нужна асинхронная обработка – инициировать её и вернуть промис.
Обратим внимание, что последний then в нашем примере ничего не возвращает. Если мы хотим, чтобы после setTimeout (*) асинхронная цепочка могла быть продолжена, то последний then тоже должен вернуть промис. Это общее правило: если внутри then стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
В данном случае промис должен перейти в состояние «выполнен» после срабатывания setTimeout .
Строку (*) для этого нужно переписать так:
.then(githubUser => < . // вместо setTimeout(() =>img.remove(), 3000); (*) return new Promise((resolve, reject) => < setTimeout(() =>< img.remove(); // после таймаута — вызов resolve, // можно без результата, чтобы управление перешло в следующий then // (или можно передать данные пользователя дальше по цепочке) resolve(); >, 3000); >); >)
Теперь, если к цепочке добавить ещё then , то он будет вызван после окончания setTimeout .
Перехват ошибок
Выше мы рассмотрели «идеальный случай» выполнения, когда ошибок нет.
А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?
Да мало ли, где ошибка…
Правило здесь очень простое.
При возникновении ошибки – она отправляется в ближайший обработчик onRejected .
Такой обработчик нужно поставить через второй аргумент .then(. onRejected) или, что то же самое, через .catch(onRejected) .
Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим catch в конец нашей цепочки:
'use strict'; // в httpGet обратимся к несуществующей странице httpGet('/page-not-exists') .then(response => JSON.parse(response)) .then(user => httpGet(`https://api.github.com/users/$`)) .then(githubUser => < githubUser = JSON.parse(githubUser); let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); return new Promise((resolve, reject) => < setTimeout(() =>< img.remove(); resolve(); >, 3000); >); >) .catch(error => < alert(error); // Error: Not Found >);
В примере выше ошибка возникает в первом же httpGet , но catch с тем же успехом поймал бы ошибку во втором httpGet или в JSON.parse .
Принцип очень похож на обычный try..catch : мы делаем асинхронную цепочку из .then , а затем, в том месте кода, где нужно перехватить ошибки, вызываем .catch(onRejected) .
А что после catch ?
Обработчик .catch(onRejected) получает ошибку и должен обработать её.
Есть два варианта развития событий:
- Если ошибка не критичная, то onRejected возвращает значение через return , и управление переходит в ближайший .then(onFulfilled) .
- Если продолжить выполнение с такой ошибкой нельзя, то он делает throw , и тогда ошибка переходит в следующий ближайший .catch(onRejected) .
Это также похоже на обычный try..catch – в блоке catch ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает throw . Существенное отличие – в том, что промисы асинхронные, поэтому при отсутствии внешнего .catch ошибка не «вываливается» в консоль и не «убивает» скрипт.
Ведь возможно, что новый обработчик .catch будет добавлен в цепочку позже.
Промисы в деталях
Самым основным источником информации по промисам является, разумеется, стандарт.
Чтобы наше понимание промисов было полным, и мы могли с лёгкостью разрешать сложные ситуации, посмотрим внимательнее, что такое промис и как он работает, но уже не в общих словах, а детально, в соответствии со стандартом ECMAScript.
Согласно стандарту, у объекта new Promise(executor) при создании есть четыре внутренних свойства:

- PromiseState – состояние, вначале «pending».
- PromiseResult – результат, при создании значения нет.
- PromiseFulfillReactions – список функций-обработчиков успешного выполнения.
- PromiseRejectReactions – список функций-обработчиков ошибки.
Когда функция-executor вызывает reject или resolve , то PromiseState становится «resolved» или «rejected» , а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь «PromiseJobs» .
Эта очередь автоматически выполняется, когда интерпретатору «нечего делать». Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как setTimeout(. 0) .
Исключение из этого правила – если resolve возвращает другой Promise . Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
Добавляет обработчики в списки один метод: .then(onResolved, onRejected) . Метод .catch(onRejected) – всего лишь сокращённая запись .then(null, onRejected) .
Он делает следующее:
- Если PromiseState == «pending» , то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.
- Иначе обработчики сразу помещаются в очередь на выполнение.
Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно – после (выполнятся в ближайшее время, через асинхронную очередь).
// Промис выполнится сразу же var promise = new Promise((resolve, reject) => resolve(1)); // PromiseState = "resolved" // PromiseResult = 1 // Добавили обработчик к выполненному промису promise.then(alert); // . он сработает тут же
Разумеется, можно добавлять и много обработчиков на один и тот же промис:
// Промис выполнится сразу же var promise = new Promise((resolve, reject) => resolve(1)); promise.then( function f1(result) < alert(result); // 1 return 'f1'; >) promise.then( function f2(result) < alert(result); // 1 return 'f2'; >)
Вид объекта promise после этого:

На этой иллюстрации можно увидеть добавленные нами обработчики f1 , f2 , а также – автоматически добавленные обработчики ошибок «Thrower» .
Дело в том, что .then , если один из обработчиков не указан, добавляет его «от себя», следующим образом:
- Для успешного выполнения – функция Identity , которая выглядит как arg => arg , то есть возвращает аргумент без изменений.
- Для ошибки – функция Thrower , которая выглядит как arg => throw arg , то есть генерирует ошибку.
Это, по сути дела, формальность, но без неё некоторые особенности поведения промисов могут «не сойтись» в общую логику, поэтому мы упоминаем о ней здесь.
Обратим внимание, в этом примере намеренно не используется чейнинг. То есть, обработчики добавляются именно на один и тот же промис.
Поэтому оба alert выдадут одно значение 1 .
Все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками в рамках одного промиса нет, а сам результат промиса ( PromiseResult ) после установки не меняется.
Поэтому, чтобы продолжить работу с результатом, используется чейнинг.
Для того, чтобы результат обработчика передать следующей функции, .then создаёт новый промис и возвращает его.
В примере выше создаётся два таких промиса (т.к. два вызова .then ), каждый из которых даёт свою ветку выполнения:

Изначально эти новые промисы – «пустые», они ждут. Когда в будущем выполнятся обработчики f1, f2 , то их результат будет передан в новые промисы по стандартному принципу:

- Если вернётся обычное значение (не промис), новый промис перейдёт в «resolved» с ним.
- Если был throw , то новый промис перейдёт в состояние «rejected» с ошибкой.
- Если вернётся промис, то используем его результат (он может быть как resolved , так и rejected ).
Дальше выполнятся уже обработчики на новом промисе, и так далее.
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
Первый промис и обработка его результата:
httpGet('/article/promise/user.json') .then(JSON.parse)
Если промис завершился через resolve , то результат – в JSON.parse , если reject – то в Thrower.
Как было сказано выше, Thrower – это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан.
Можно считать, что второй обработчик выглядит так:
httpGet('/article/promise/user.json') .then(JSON.parse, err => throw err)
Заметим, что когда обработчик в промисах делает throw – в данном случае, при ошибке запроса, то такая ошибка не «валит» скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик onRejected .
Добавим в код ещё строку:
httpGet('/article/promise/user.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`))
Цепочка «выросла вниз»:
Функция JSON.parse либо возвращает объект с данными, либо генерирует ошибку (что расценивается как reject ).
Если всё хорошо, то then(user => httpGet(…)) вернёт новый промис, на который стоят уже два обработчика:
httpGet('/article/promise/user.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`)) .then( JSON.parse, function avatarError(error) < if (error.code == 404) < return ; > else < throw error; >> >)
Наконец-то хоть какая-то обработка ошибок!
Обработчик avatarError перехватит ошибки, которые были ранее. Функция httpGet при генерации ошибки записывает её HTTP-код в свойство error.code , так что мы легко можем понять – что это:
- Если страница на Github не найдена – можно продолжить выполнение, используя «аватар по умолчанию»
- Иначе – пробрасываем ошибку дальше.
Итого, после добавления оставшейся части цепочки, картина получается следующей:
'use strict'; httpGet('/article/promise/userNoGithub.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`)) .then( JSON.parse, function githubError(error) < if (error.code == 404) < return ; > else < throw error; >> ) .then(function showAvatar(githubUser) < let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); setTimeout(() =>img.remove(), 3000); >) .catch(function genericError(error) < alert(error); // Error: Not Found >);
В конце срабатывает общий обработчик genericError , который перехватывает любые ошибки. В данном случае ошибки, которые в него попадут, уже носят критический характер, что-то серьёзно не так. Чтобы посетитель не удивился отсутствию информации, мы показываем ему сообщение об этом.
Можно и как-то иначе вывести уведомление о проблеме, главное – не забыть обработать ошибки в конце. Если последнего catch не будет, а цепочка завершится с ошибкой, то посетитель об этом не узнает.
В консоли тоже ничего не будет, так как ошибка остаётся «внутри» промиса, ожидая добавления следующего обработчика onRejected , которому будет передана.
Итак, мы рассмотрели основные приёмы использования промисов. Далее – посмотрим некоторые полезные вспомогательные методы.
Параллельное выполнение
Что, если мы хотим осуществить несколько асинхронных процессов одновременно и обработать их результат?
В классе Promise есть следующие статические методы.
Promise.all(iterable)
Вызов Promise.all(iterable) получает массив (или другой итерируемый объект) промисов и возвращает промис, который ждёт, пока все переданные промисы завершатся, и переходит в состояние «выполнено» с массивом их результатов.
Promise.all([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json') ]).then(results => < alert(results); >);
Допустим, у нас есть массив с URL.
let urls = [ '/article/promise/user.json', '/article/promise/guest.json' ];
Чтобы загрузить их параллельно, нужно:
- Создать для каждого URL соответствующий промис.
- Обернуть массив таких промисов в Promise.all .
'use strict'; let urls = [ '/article/promise/user.json', '/article/promise/guest.json' ]; Promise.all( urls.map(httpGet) ) .then(results => < alert(results); >);
Заметим, что если какой-то из промисов завершился с ошибкой, то результатом Promise.all будет эта ошибка. При этом остальные промисы игнорируются.
Promise.all([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json'), httpGet('/article/promise/no-such-page.json') // (нет такой страницы) ]).then( result => alert("не сработает"), error => alert("Ошибка: " + error.message) // Ошибка: Not Found )
Promise.race(iterable)
Вызов Promise.race , как и Promise.all , получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис.
Но, в отличие от Promise.all , результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
Promise.race([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json') ]).then(firstResult => < firstResult = JSON.parse(firstResult); alert( firstResult.name ); // iliakan или guest, смотря что загрузится раньше >);
Promise.resolve(value)
Вызов Promise.resolve(value) создаёт успешно выполнившийся промис с результатом value .
Он аналогичен конструкции:
new Promise((resolve) => resolve(value))
Promise.resolve используют, когда хотят построить асинхронную цепочку, и начальный результат уже есть.
Promise.resolve(window.location) // начать с этого значения .then(httpGet) // вызвать для него httpGet .then(alert) // и вывести результат
Promise.reject(error)
Аналогично Promise.reject(error) создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой error .
Promise.reject(new Error(". ")) .catch(alert) // Error: .
Метод Promise.reject используется очень редко, гораздо реже чем resolve , потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
Итого
- Промис – это специальный объект, который хранит своё состояние, текущий результат (если есть) и колбэки.
- При создании new Promise((resolve, reject) => . ) автоматически запускается функция-аргумент, которая должна вызвать resolve(result) при успешном выполнении и reject(error) – при ошибке.
- Аргумент resolve/reject (только первый, остальные игнорируются) передаётся обработчикам на этом промисе.
- Обработчики назначаются вызовом .then/catch .
- Для передачи результата от одного обработчика к другому используется чейнинг.
У промисов есть некоторые ограничения. В частности, стандарт не предусматривает какой-то метод для «отмены» промиса, хотя в ряде ситуаций (http-запросы) это было бы довольно удобно. Возможно, он появится в следующей версии стандарта JavaScript.
В современной JavaScript-разработке сложные цепочки с промисами используются редко, так как они куда проще описываются при помощи генераторов с библиотекой co , которые рассмотрены в соответствующей главе. Можно сказать, что промисы лежат в основе более продвинутых способов асинхронной разработки.
Задачи
Промисифицировать setTimeout
Напишите функцию delay(ms) , которая возвращает промис, переходящий в состояние «resolved» через ms миллисекунд.
delay(1000) .then(() => alert("Hello!"))
Такая функция полезна для использования в других промис-цепочках.
Вот такой вызов:
return new Promise((resolve, reject) => < setTimeout(() =>< doSomeThing(); resolve(); >, ms) >);
Станет возможным переписать так:
return delay(ms).then(doSomething);
On promise что это
Промис (promise) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Асинхронная операция, упрощенно говоря, это некоторое действие, выполняется независимо от окружающего ее кода, в котором она вызывается, не блокирует выполнение вызываемого кода.
Промис может находиться в одном из следующих состояний:
- pending (состояние ожидания): начальное состояние, промис создан, но выполнение еще не завершено
- fulfilled (успешно завершено): действие, которое представляет промис, успешно завершено
- rejected (завершено с ошибкой): при выполнении действия, которое представляет промис, произошла ошибка
Для создания промиса применяется конструктор типа Promise :
new Promise(executor)
В качестве параметра конструктор принимает функцию, которая выполняется при создании промиса. Обычно эта функция представляет асинхронные операции, которые занимают продолжительное время. Например, определим простейший промис:
const myPromise = new Promise(function()< console.log("Выполнение асинхронной операции"); >);
Здесь функция просто выводит на консоль сообщение. Соответственно при выполнении этого кода мы увидим на консоли сообщение «Выполнение асинхронной операции» .
При создании промиса, когда его функция еще не начала выполняться, промис переходит в состояние «pending», то есть ожидает выполнения.
Для эмуляции асинхронности определим несколько промисов:
const myPromise3000 = new Promise(function()< console.log("[myPromise3000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise3000] Завершение асинхронной операции"), 3000); >); const myPromise1000 = new Promise(function()< console.log("[myPromise1000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise1000] Завершение асинхронной операции"), 1000); >); const myPromise2000 = new Promise(function()< console.log("[myPromise2000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise2000] Завершение асинхронной операции"), 2000); >);
Здесь определены три однотипных промиса. Чтобы каждый из них не выполнялся сразу, они используют функцию setTimeout с задержкой в несколько секунд. Для разных промисов длительность задержки различается. И в данном случае мы получим следующий консольный вывод:
[myPromise3000] Выполнение асинхронной операции [myPromise1000] Выполнение асинхронной операции [myPromise2000] Выполнение асинхронной операции [myPromise1000] Завершение асинхронной операции [myPromise2000] Завершение асинхронной операции [myPromise3000] Завершение асинхронной операции
Здесь мы видим, что первым начал выполняться промис myPromise3000, однако он же завершился последним, так как для него установлено наибольшее время задержки — 3 секунды. Однако его задержка не помешала выполнению остальных промисов.
resolve и reject
Как правило, функция, которая передается в конструктор Promise, принимает два параметра:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); >);
Оба этих параметра — resolve и reject также представляют функции. И каждая из этих функций принимает параметр любого типа.
Первый параметр — функция resolve вызывается в случае успешного выполнения. Мы можем в нее передать значение, которое мы можем получить в результате успешного выполнения.
Второй параметр — функция reject вызывается, если выполнение операции завершилось с ошибкой. Мы можем в нее передать значение, которое представит некоторую информацию об ошибке.
Успешное выполнение промиса
Итак, первый параметр функции в конструкторе Promise — функция resolve выполняется при успешном выполненим. В эту функцию обычно передается значение, которое представляет результат операции при успешном выполнении. Это значение может представлять любой объект. Например, передадим в эту функцию строку:
const myPromise = new Promise(function(resolve)< console.log("Выполнение асинхронной операции"); resolve("Привет мир!"); >);
Функция resolve() вызывается в конце выполняемой операции после всех действий. При вызове этой функции промис переходит в состояние fulfilled (успешно выполнено).
При этом стоит отметить, что теоретически мы можем возвратить из функции результат, но практического смысла в этом не будет:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); return "Привет мир!"; >);
Данное возвращаемое значение мы не сможем передать во вне. И если действительно надо возвратить какой-то результат, то он передается в функцию resolve() .
Передача информации об ошибке
Второй параметр функции в конструкторе Promise — функция reject вызывается при возникновении ошибки. В эту функцию обычно передается некоторая информация об ошибке, которое может представлять любой объект. Например:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); reject("Переданы некорректные данные"); >);
При вызове функции reject() промис переходит в состояние rejected (завершилось с ошибкой).
Объединение resolve и reject
Естественно мы можем определить логику, при которой в зависимости от условий будут выполняться обе функции:
const x = 4; const y = 0; const myPromise = new Promise(function(resolve, reject) < if(y === 0) < reject("Переданы некорректные данные"); >else < const z = x / y; resolve(z); >>);
В данном случае, если значени константы y равно 0, то сообщаем об ошибке, вызывая функцию reject() . Если не равно 0, то выполняем операцию деления и передаем результат в функцию resolve() .
Промисы
Представьте, что вы известный певец, которого фанаты постоянно донимают расспросами о предстоящем сингле.
Чтобы получить передышку, вы обещаете разослать им сингл, когда он будет выпущен. Вы даёте фанатам список, в который они могут записаться. Они могут оставить там свой e-mail, чтобы получить песню, как только она выйдет. И даже больше: если что-то пойдёт не так, например, в студии будет пожар и песню выпустить не выйдет, они также получат уведомление об этом.
Все счастливы! Вы счастливы, потому что вас больше не донимают фанаты, а фанаты больше не беспокоятся, что пропустят новый сингл.
Это аналогия из реальной жизни для ситуаций, с которыми мы часто сталкиваемся в программировании:
- Есть «создающий» код, который делает что-то, что занимает время. Например, загружает данные по сети. В нашей аналогии это – «певец».
- Есть «потребляющий» код, который хочет получить результат «создающего» кода, когда он будет готов. Он может быть необходим более чем одной функции. Это – «фанаты».
- Promise (по англ. promise , будем называть такой объект «промис») – это специальный объект в JavaScript, который связывает «создающий» и «потребляющий» коды вместе. В терминах нашей аналогии – это «список для подписки». «Создающий» код может выполняться сколько потребуется, чтобы получить результат, а промис делает результат доступным для кода, который подписан на него, когда результат готов.
Аналогия не совсем точна, потому что объект Promise в JavaScript гораздо сложнее простого списка подписок: он обладает дополнительными возможностями и ограничениями. Но для начала и такая аналогия хороша.
Синтаксис создания Promise :
let promise = new Promise(function(resolve, reject) < // функция-исполнитель (executor) // "певец" >);
Функция, переданная в конструкцию new Promise , называется исполнитель (executor). Когда Promise создаётся, она запускается автоматически. Она должна содержать «создающий» код, который когда-нибудь создаст результат. В терминах нашей аналогии: исполнитель – это «певец».
Её аргументы resolve и reject – это колбэки, которые предоставляет сам JavaScript. Наш код – только внутри исполнителя.
Когда он получает результат, сейчас или позже – не важно, он должен вызвать один из этих колбэков:
- resolve(value) — если работа завершилась успешно, с результатом value .
- reject(error) — если произошла ошибка, error – объект ошибки.
Итак, исполнитель запускается автоматически, он должен выполнить работу, а затем вызвать resolve или reject .
У объекта promise , возвращаемого конструктором new Promise , есть внутренние свойства:
- state («состояние») — вначале «pending» («ожидание»), потом меняется на «fulfilled» («выполнено успешно») при вызове resolve или на «rejected» («выполнено с ошибкой») при вызове reject .
- result («результат») — вначале undefined , далее изменяется на value при вызове resolve(value) или на error при вызове reject(error) .
Так что исполнитель по итогу переводит promise в одно из двух состояний:
Позже мы рассмотрим, как «фанаты» узнают об этих изменениях.
Ниже пример конструктора Promise и простого исполнителя с кодом, дающим результат с задержкой (через setTimeout ):
let promise = new Promise(function(resolve, reject) < // эта функция выполнится автоматически, при вызове new Promise // через 1 секунду сигнализировать, что задача выполнена с результатом "done" setTimeout(() =>resolve("done"), 1000); >);
Мы можем наблюдать две вещи, запустив код выше:
- Функция-исполнитель запускается сразу же при вызове new Promise .
- Исполнитель получает два аргумента: resolve и reject — это функции, встроенные в JavaScript, поэтому нам не нужно их писать. Нам нужно лишь позаботиться, чтобы исполнитель вызвал одну из них по готовности.
Спустя одну секунду «обработки» исполнитель вызовет resolve(«done») , чтобы передать результат:
Это был пример успешно выполненной задачи, в результате мы получили «успешно выполненный» промис.
А теперь пример, в котором исполнитель сообщит, что задача выполнена с ошибкой:
let promise = new Promise(function(resolve, reject) < // спустя одну секунду будет сообщено, что задача выполнена с ошибкой setTimeout(() =>reject(new Error("Whoops!")), 1000); >);
Подведём промежуточные итоги: исполнитель выполняет задачу (что-то, что обычно требует времени), затем вызывает resolve или reject , чтобы изменить состояние соответствующего Promise .
Промис – и успешный, и отклонённый будем называть «завершённым», в отличие от изначального промиса «в ожидании».
Может быть что-то одно: либо результат, либо ошибка
Исполнитель должен вызвать что-то одно: resolve или reject . Состояние промиса может быть изменено только один раз.
Все последующие вызовы resolve и reject будут проигнорированы:
let promise = new Promise(function(resolve, reject) < resolve("done"); reject(new Error("…")); // игнорируется setTimeout(() =>resolve("…")); // игнорируется >);
Идея в том, что задача, выполняемая исполнителем, может иметь только один итог: результат или ошибку.
Также заметим, что функция resolve / reject ожидает только один аргумент (или ни одного). Все дополнительные аргументы будут проигнорированы.
Вызывайте reject с объектом Error
В случае, если что-то пошло не так, мы должны вызвать reject . Это можно сделать с аргументом любого типа (как и resolve ), но рекомендуется использовать объект Error (или унаследованный от него). Почему так? Скоро нам станет понятно.
Вызов resolve / reject сразу
Обычно исполнитель делает что-то асинхронное и после этого вызывает resolve / reject , то есть через какое-то время. Но это не обязательно, resolve или reject могут быть вызваны сразу:
let promise = new Promise(function(resolve, reject) < // задача, не требующая времени resolve(123); // мгновенно выдаст результат: 123 >);
Это может случиться, например, когда мы начали выполнять какую-то задачу, но тут же увидели, что ранее её уже выполняли, и результат закеширован.
Такая ситуация нормальна. Мы сразу получим успешно завершённый Promise .
Свойства state и result – внутренние
Свойства state и result – это внутренние свойства объекта Promise и мы не имеем к ним прямого доступа. Для обработки результата следует использовать методы .then / .catch / .finally , про них речь пойдёт дальше.
Потребители: then, catch
Объект Promise служит связующим звеном между исполнителем («создающим» кодом или «певцом») и функциями-потребителями («фанатами»), которые получат либо результат, либо ошибку. Функции-потребители могут быть зарегистрированы (подписаны) с помощью методов .then и .catch .
then
Наиболее важный и фундаментальный метод – .then .
promise.then( function(result) < /* обработает успешное выполнение */ >, function(error) < /* обработает ошибку */ >);
Первый аргумент метода .then – функция, которая выполняется, когда промис переходит в состояние «выполнен успешно», и получает результат.
Второй аргумент .then – функция, которая выполняется, когда промис переходит в состояние «выполнен с ошибкой», и получает ошибку.
Например, вот реакция на успешно выполненный промис:
let promise = new Promise(function(resolve, reject) < setTimeout(() =>resolve("done!"), 1000); >); // resolve запустит первую функцию, переданную в .then promise.then( result => alert(result), // выведет "done!" через одну секунду error => alert(error) // не будет запущена );
Выполнилась первая функция.
А в случае ошибки в промисе – выполнится вторая:
let promise = new Promise(function(resolve, reject) < setTimeout(() =>reject(new Error("Whoops!")), 1000); >); // reject запустит вторую функцию, переданную в .then promise.then( result => alert(result), // не будет запущена error => alert(error) // выведет "Error: Whoops!" спустя одну секунду );
Если мы заинтересованы только в результате успешного выполнения задачи, то в then можно передать только одну функцию:
let promise = new Promise(resolve => < setTimeout(() =>resolve("done!"), 1000); >); promise.then(alert); // выведет "done!" спустя одну секунду
catch
Если мы хотели бы только обработать ошибку, то можно использовать null в качестве первого аргумента: .then(null, errorHandlingFunction) . Или можно воспользоваться методом .catch(errorHandlingFunction) , который сделает то же самое:
let promise = new Promise((resolve, reject) => < setTimeout(() =>reject(new Error("Ошибка!")), 1000); >); // .catch(f) это то же самое, что promise.then(null, f) promise.catch(alert); // выведет "Error: Ошибка!" спустя одну секунду
Вызов .catch(f) – это сокращённый, «укороченный» вариант .then(null, f) .
Очистка: finally
По аналогии с блоком finally из обычного try <. >catch <. >, у промисов также есть метод finally .
Вызов .finally(f) похож на .then(f, f) , в том смысле, что f выполнится в любом случае, когда промис завершится: успешно или с ошибкой.
Идея finally состоит в том, чтобы настроить обработчик для выполнения очистки/доведения после завершения предыдущих операций.
Например, остановка индикаторов загрузки, закрытие больше не нужных соединений и т.д.
Думайте об этом как о завершении вечеринки. Независимо от того, была ли вечеринка хорошей или плохой, сколько на ней было друзей, нам все равно нужно (или, по крайней мере, мы должны) сделать уборку после нее.
Код может выглядеть следующим образом:
new Promise((resolve, reject) => < /* сделать что-то, что займёт время, и после вызвать resolve или может reject */ >) // выполнится, когда промис завершится, независимо от того, успешно или нет .finally(() => остановить индикатор загрузки) // таким образом, индикатор загрузки всегда останавливается, прежде чем мы продолжим .then(result => показать результат, err => показать ошибку)
Обратите внимание, что finally(f) – это не совсем псевдоним then(f,f) , как можно было подумать.
Есть важные различия:
- Обработчик, вызываемый из finally , не имеет аргументов. В finally мы не знаем, как был завершён промис. И это нормально, потому что обычно наша задача – выполнить «общие» завершающие процедуры. Пожалуйста, взгляните на приведенный выше пример: как вы можете видеть, обработчик finally не имеет аргументов, а результат promise обрабатывается в следующем обработчике.
- Обработчик finally «пропускает» результат или ошибку дальше, к последующим обработчикам. Например, здесь результат проходит через finally к then :
new Promise((resolve, reject) => < setTimeout(() =>resolve("value"), 2000); >) .finally(() => alert("Промис завершён")) // срабатывает первым .then(result => alert(result)); //
Как вы можете видеть, значение возвращаемое первым промисом, передается через finally к следующему then . Это очень удобно, потому что finally не предназначен для обработки результата промиса. Как уже было сказано, это место для проведения общей очистки, независимо от того, каков был результат. А здесь ошибка из промиса проходит через finally к catch :
new Promise((resolve, reject) => < throw new Error("error"); >) .finally(() => alert("Промис завершён")) // срабатывает первым .catch(err => alert(err)); //
- Обработчик finally не получает результат предыдущего обработчика (у него нет аргументов). Вместо этого этот результат передается следующему подходящему обработчику.
- Если обработчик finally возвращает что-то, это игнорируется.
- Когда finally выдает ошибку, выполнение переходит к ближайшему обработчику ошибок.
Эти функции полезны и заставляют все работать правильно, если мы используем finally так, как предполагается: для общих процедур очистки.
На завершённых промисах обработчики запускаются сразу
Если промис в состоянии ожидания, обработчики в .then/catch/finally будут ждать его.
Иногда может случиться так, что промис уже выполнен, когда мы добавляем к нему обработчик.
В таком случае эти обработчики просто запускаются немедленно:
// при создании промиса он сразу переводится в состояние "успешно завершён" let promise = new Promise(resolve => resolve("готово!")); promise.then(alert); // готово! (выведется сразу)
Пример: loadScript
Теперь рассмотрим несколько практических примеров того, как промисы могут облегчить нам написание асинхронного кода.
У нас есть функция loadScript для загрузки скрипта из предыдущей главы.
Давайте вспомним, как выглядел вариант с колбэками:
function loadScript(src, callback) < let script = document.createElement('script'); script.src = src; script.onload = () =>callback(null, script); script.onerror = () => callback(new Error(`Ошибка загрузки скрипта $`)); document.head.append(script); >
Теперь перепишем её, используя Promise .
Новой функции loadScript более не нужен аргумент callback . Вместо этого она будет создавать и возвращать объект Promise , который перейдет в состояние «успешно завершён», когда загрузка закончится. Внешний код может добавлять обработчики («подписчиков»), используя .then :
function loadScript(src) < return new Promise(function(resolve, reject) < let script = document.createElement('script'); script.src = src; script.onload = () =>resolve(script); script.onerror = () => reject(new Error(`Ошибка загрузки скрипта $`)); document.head.append(script); >); >
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`$ загружен!`), error => alert(`Ошибка: $`) ); promise.then(script => alert('Ещё один обработчик. '));
Сразу заметно несколько преимуществ перед подходом с использованием колбэков:
| Промисы | Колбэки |
|---|---|
| Промисы позволяют делать вещи в естественном порядке. Сперва мы запускаем loadScript(script) , и затем ( .then ) мы пишем, что делать с результатом. | У нас должна быть функция callback на момент вызова loadScript(script, callback) . Другими словами, нам нужно знать что делать с результатом до того, как вызовется loadScript . |
| Мы можем вызывать .then у Promise столько раз, сколько захотим. Каждый раз мы добавляем нового «фаната», новую функцию-подписчика в «список подписок». Больше об этом в следующей главе: Цепочка промисов. | Колбэк может быть только один. |
Таким образом, промисы позволяют улучшить порядок кода и дают нам гибкость. Но это далеко не всё. Мы узнаем ещё много полезного в последующих главах.