Как получить promise result js
Ранее мы рассмотрели, как из функции промиса мы можем передать во вне результат асинхронной операции:
const myPromise = new Promise(function(resolve)< console.log("Выполнение асинхронной операции"); resolve("Привет мир!"); >);
Теперь получим это значение. Для получения результата операции промиса применяется функция then() объекта Promise :
then(onFulfilled, onRejected);
Первый параметр функции — onFulfilled представляет функцию, которая выполняется при успешном завершении промиса и в качестве параметра получает переданные в resolve() данные.
Второй параметр функции — onRejected представляет функцию, которая выполняется при возникновении ошибки и в качестве параметра получает переданные в reject() данные.
Функция then() возвращает также объект Promise .
Так, получим переданные данные:
const myPromise = new Promise(function(resolve)< console.log("Выполнение асинхронной операции"); resolve("Привет мир!"); >); myPromise.then(function(value)< console.log(`Из промиса получены данные: $`); >)
То есть параметр value здесь будет представлять строку «Привет мир!» , которая передается в resolve(«Привет мир!») . В итоге консольный вывод будет выглядеть следующим образом:
Выполнение асинхронной операции Из промиса получены данные: Привет мир!
При этом нам необязательно вообще передавать в resolve() какое-либо значение. Возможно, асинхронная операция просто выполняется и передает во вне никакого результата.
const x = 4; const y = 8; const myPromise = new Promise(function()< console.log("Выполнение асинхронной операции"); const z = x + y; console.log(`Результат операции: $`) >); myPromise.then();
В данном случае функция в промисе вычисляет сумму чисел x и y и выводит результат на консоль.
Метод Promise.resolve
Иногда требуется просто вернуть из промиса некоторое значение. Для этого можно использовать метод Promise.resolve() . В этот метод передается возвращаемое из промиса значение. Метод Promise.resolve() возвращает объект Promise:
const myPromise = Promise.resolve("Привет мир!"); myPromise.then(value => console.log(value)); // Привет мир!
Определение промиса через функцию
Нередко промис определяется через функцию, которая возвращет объект Promise. Например:
function sum(x, y)< return new Promise(function(resolve)< const result = x + y; resolve(result); >) > sum(3, 5).then(function(value)< console.log("Результат операции:", value);>); sum(25, 4).then(function(value)< console.log("Сумма чисел:", value);>);
Здесь функция sum() принимает два числа и возвращает промис, который инкапсулирует операцию сумму этих чисел. После вычисления сумма чисел передается в resolve() , соответственно мы ее затем можем получить через метод then() . Определение промиса через функцию позволяет нам, с одной стороны, при вызове функции передавать разные значения. А с другой стороны, работать с результатом этой функции как с промисом и настроить при каждом конкретном вызове обработку полученного значения.
Результат работы программы:
Результат операции: 8 Сумма чисел: 29
Однако, что если у нас совпадает принцип обработки полученного из асинхронной функции значения?
sum(3, 5).then(function(value)< console.log("Результат операции:", value);>); sum(25, 4).then(function(value)< console.log("Результат операции:", value);>);
В этом случае логика обработки будет повторяться. Но поскольку метод then() также возвращает объект Promise, то мы можем сделать следующим образом:
function sum(x, y)< return new Promise(function(resolve)< const result = x + y; resolve(result); >).then(function(value)< console.log("Результат операции:", value);>); > sum(3, 5); sum(25, 4);
Гибкая настройка функции
А что, если мы хотим, чтобы у программиста был выбор: если он хочет, то может определить свой обработчик, а если нет, то применяется некоторый обработчик по умолчанию. В этом случае мы можем определить функцию обработчика в качестве параметра функции, а если он не передан, то устанавливать обработчик по умолчанию:
function sum(x, y, func)< // если обработчик не установлен, то устанавливаем обработчик по умолчанию if(func===undefined) func = function(value)< console.log("Результат операции:", value);>; return new Promise(function(resolve)< const result = x + y; resolve(result); >).then(func); > sum(3, 5); sum(25, 4, function(value)< console.log("Сумма:", value);>);
Здесь при первом вызове функции sum() ( sum(3, 5) ) будет срабатывать обработчик по умолчанию. Во втором случае обработчик явным образом передается через третий параметр, соответственно он будет задействован sum(25, 4, function(value)< console.log("Сумма:", value);>)
Promise.resolve()
Метод Promise.resolve(value) возвращает Promise выполненный с переданным значением. Если переданное значение является thenable — объект (т.е. имеет метод «then» method ), возвращаемый промис будет следовать thenable — объекту, принимая своё состояние; в ином случае возвращаемый промис будет выполнен с переданным значением.
Синтаксис
Promise.resolve(value); Promise.resolve(promise); Promise.resolve(thenable);
Параметры
Значение с которым будет выполнен промис. Может также быть промисом или объект подобный промису (thenable — объект имеющий метод then).
Возвращаемое значение
Выполненный с переданным значением Promise .
Описание
Метод Promise.resolve возвращает выполненное промис ( Promise ).
Примеры
Использование метода Promise.resolve
.resolve("Success").then( function (value) console.log(value); // "Success" >, function (value) // не будет вызвана >, );
Выполнение с массивом
var p = Promise.resolve([1, 2, 3]); p.then(function (v) console.log(v[0]); // 1 >);
Выполнение с другим промисом ( Promise )
var original = Promise.resolve(true); var cast = Promise.resolve(original); cast.then(function (v) console.log(v); // true >);
Выполнение с thenable объектом и выбрасывание исключений
// Выполнение с thenable объектом var p1 = Promise.resolve( then: function (onFulfill, onReject) onFulfill("fulfilled!"); >, >); console.log(p1 instanceof Promise); // true p1.then( function (v) console.log(v); // "fulfilled!" >, function (e) // не вызывается >, ); // Thenable объект выбрасывает исключение // перед вызовом колбэка Promise resolves var thenable = then: function (resolve) throw new TypeError("Throwing"); resolve("Resolving"); >, >; var p2 = Promise.resolve(thenable); p2.then( function (v) // не вызывается >, function (e) console.log(e); // TypeError: Throwing >, ); // Thenable объект выбрасывает исключение // после вызова колбэка Promise resolves var thenable = then: function (resolve) resolve("Resolving"); throw new TypeError("Throwing"); >, >; var p3 = Promise.resolve(thenable); p3.then( function (v) console.log(v); // "Resolving" >, function (e) // не вызывается >, );
Спецификация
Specification |
---|
ECMAScript Language Specification # sec-promise.resolve |
Совместимость с браузерами
BCD tables only load in the browser
Использование промисов
Promise (промис) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Так как большинство людей пользуются уже созданными промисами, это руководство начнём с объяснения использования вернувшихся промисов до объяснения принципов создания.
В сущности, промис — это возвращаемый объект, в который вы записываете два колбэка вместо того, чтобы передать их функции.
Например, вместо старомодной функции, которая принимает два колбэка и вызывает один из них в зависимости от успешного или неудачного завершения операции:
function doSomethingOldStyle(successCallback, failureCallback) console.log("Готово."); // Успех в половине случаев. if (Math.random() > 0.5) successCallback("Успех"); > else failureCallback("Ошибка"); > > function successCallback(result) console.log("Успешно завершено с результатом " + result); > function failureCallback(error) console.log("Завершено с ошибкой " + error); > doSomethingOldStyle(successCallback, failureCallback);
…современные функции возвращают промис, в который вы записываете ваши колбэки:
function doSomething() return new Promise((resolve, reject) => console.log("Готово."); // Успех в половине случаев. if (Math.random() > 0.5) resolve("Успех"); > else reject("Ошибка"); > >); > const promise = doSomething(); promise.then(successCallback, failureCallback);
doSomething().then(successCallback, failureCallback);
Мы называем это асинхронным вызовом функции. У этого соглашения есть несколько преимуществ. Давайте рассмотрим их.
Гарантии
В отличие от старомодных переданных колбэков промис даёт некоторые гарантии:
- Колбэки никогда не будут вызваны до завершения обработки текущего события в событийном цикле JavaScript.
- Колбэки, добавленные через .then даже после успешного или неудачного завершения асинхронной операции, будут также вызваны.
- Несколько колбэков может быть добавлено вызовом .then нужное количество раз, и они будут выполняться независимо в порядке добавления.
Но наиболее непосредственная польза от промисов — цепочка вызовов (chaining).
Цепочка вызовов
Общая нужда — выполнять две или более асинхронных операции одна за другой, причём каждая следующая начинается при успешном завершении предыдущей и использует результат её выполнения. Мы реализуем это, создавая цепочку вызовов промисов (promise chain).
Вот в чём магия: функция then возвращает новый промис, отличающийся от первоначального:
let promise = doSomething(); let promise2 = promise.then(successCallback, failureCallback);
let promise2 = doSomething().then(successCallback, failureCallback);
Второй промис представляет завершение не только doSomething() , но и функций successCallback или failureCallback , переданных вами, а они тоже могут быть асинхронными функциями, возвращающими промис. В этом случае все колбэки, добавленные к promise2 будут поставлены в очередь за промисом, возвращаемым successCallback или failureCallback .
По сути, каждый вызванный промис означает успешное завершение предыдущих шагов в цепочке.
Раньше выполнение нескольких асинхронных операций друг за другом приводило к классической «Вавилонской башне» колбэков:
doSomething(function (result) doSomethingElse( result, function (newResult) doThirdThing( newResult, function (finalResult) console.log("Итоговый результат: " + finalResult); >, failureCallback, ); >, failureCallback, ); >, failureCallback);
В современных функциях мы записываем колбэки в возвращаемые промисы — формируем цепочку промисов:
doSomething() .then(function (result) return doSomethingElse(result); >) .then(function (newResult) return doThirdThing(newResult); >) .then(function (finalResult) console.log("Итоговый результат: " + finalResult); >) .catch(failureCallback);
Аргументы then необязательны, а catch(failureCallback) — это сокращение для then(null, failureCallback) . Вот как это выражено с помощью стрелочных функций:
doSomething() .then((result) => doSomethingElse(result)) .then((newResult) => doThirdThing(newResult)) .then((finalResult) => console.log(`Итоговый результат: $finalResult>`); >) .catch(failureCallback);
Важно: Всегда возвращайте промисы в return, иначе колбэки не будут сцеплены и ошибки могут быть не пойманы (стрелочные функции неявно возвращают результат, если скобки <> вокруг тела функции опущены).
Цепочка вызовов после catch
Можно продолжить цепочку вызовов после ошибки, т. е. после catch , что полезно для выполнения новых действий даже после того, как действие вернёт ошибку в цепочке вызовов. Ниже приведён пример:
new Promise((resolve, reject) => < console.log('Начало'); resolve(); >) .then(() => < throw new Error('Где-то произошла ошибка'); console.log('Выведи это'); >) .catch(() => < console.log('Выведи то'); >) .then(() => < console.log('Выведи это, несмотря ни на что'); >);
В результате выведется данный текст:
Начало Выведи то Выведи это, несмотря ни на что
Заметьте, что текст «Выведи это» не вывелся, потому что «Где-то произошла ошибка» привела к отказу
Распространение ошибки
Вы могли ранее заметить, что failureCallback повторяется три раза в «pyramid of doom», а в цепочке промисов всего лишь один раз:
doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => console.log(`Итоговый результат: $`)) .catch(failureCallback);
В основном, цепочка промисов останавливает выполнение кода, если где-либо произошла ошибка, и вместо этого ищет далее по цепочке обработчики ошибок. Это очень похоже на то, как работает синхронный код:
try < let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Итоговый результат: $`); > catch(error)
Эта симметрия с синхронным кодом лучше всего показывает себя в синтаксическом сахаре async / await в ECMAScript 2017:
async function foo() < try < let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Итоговый результат: $`); > catch(error) < failureCallback(error); >>
Работа данного кода основана на промисах. Для примера здесь используется функция doSomething() , которая встречалась ранее. Вы можете прочитать больше о синтаксисе здесь
Промисы решают основную проблему пирамид, обработку всех ошибок, даже вызовов исключений и программных ошибок. Это основа для функционального построения асинхронных операций.
Создание промиса вокруг старого колбэка
Promise может быть создан с помощью конструктора. Это может понадобится только для старых API.
В идеале, все асинхронные функции уже должны возвращать промис. Но увы, некоторые APIs до сих пор ожидают успешного или неудачного колбэка переданных по старинке. Типичный пример: setTimeout() функция:
setTimeout(() => saySomething("10 seconds passed"), 10000);
Смешивание старого колбэк-стиля и промисов проблематично. В случае неудачного завершения saySomething или программной ошибки, нельзя обработать ошибку.
К счастью мы можем обернуть функцию в промис. Хороший тон оборачивать проблематичные функции на самом низком возможном уровне, и больше никогда их не вызывать напрямую:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
В сущности, конструктор промиса становится исполнителем функции, который позволяет нам резолвить или режектить промис вручную. Так как setTimeout всегда успешен, мы опустили reject в этом случае.
Композиция
Promise.resolve() и Promise.reject() короткий способ создать уже успешные или отклонённые промисы соответственно. Это иногда бывает полезно.
Promise.all() и Promise.race() — два метода запустить асинхронные операции параллельно.
Последовательное выполнение композиции возможно при помощи хитрости JavaScript:
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
Фактически, мы превращаем массив асинхронных функций в цепочку промисов равносильно: Promise.resolve().then(func1).then(func2);
Это также можно сделать, объединив композицию в функцию, в функциональном стиле программирования:
const applyAsync = (acc,val) => acc.then(val); const composeAsync = (. funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
composeAsync функция примет любое количество функций в качестве аргументов и вернёт новую функцию которая примет в параметрах начальное значение, переданное по цепочке. Это удобно, потому что некоторые или все функции могут быть либо асинхронными, либо синхронными, и они гарантированно выполнятся в правильной последовательности:
const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2); transformData(data);
В ECMAScript 2017, последовательные композиции могут быть выполнены более простым способом с помощью async/await:
for (const f of [func1, func2])
Порядок выполнения
Чтобы избежать сюрпризов, функции, переданные в then никогда не будут вызваны синхронно, даже с уже разрешённым промисом:
Promise.resolve().then(() => console.log(2)); console.log(1); // 1, 2
Вместо немедленного выполнения, переданная функция встанет в очередь микрозадач, а значит выполнится, когда очередь будет пустой в конце текущего вызова JavaScript цикла событий (event loop), т.е. очень скоро:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait().then(() => console.log(4)); Promise.resolve().then(() => console.log(2)).then(() => console.log(3)); console.log(1); // 1, 2, 3, 4
Вложенность
Простые цепочки promise лучше оставлять без вложений, так как вложенность может быть результатом небрежной структуры. Смотрите распространённые ошибки.
Вложенность — это управляющая структура, ограничивающая область действия операторов catch. В частности, вложенный catch только перехватывает сбои в своей области и ниже, а не ошибки выше в цепочке за пределами вложенной области. При правильном использовании это даёт большую точность в извлечение ошибок:
doSomethingCritical() .then(result => doSomethingOptional() .then(optionalResult => doSomethingExtraNice(optionalResult)) .catch(e => <>)) // Игнорируется если необязательные параметр не выкинул исключение .then(() => moreCriticalStuff()) .catch(e => console.log("Критическая ошибка: " + e.message));
Обратите внимание, что необязательный шаги здесь выделены отступом.
Внутренний оператор catch нейтрализует и перехватывает ошибки только от doSomethingOptional() и doSomethingExtraNice(), после чего код возобновляется с помощью moreCriticalStuff(). Важно, что в случае сбоя doSomethingCritical() его ошибка перехватывается только последним (внешним) catch.
Частые ошибки
В этом разделе собраны частые ошибки, возникающие при создании цепочек промисов. Несколько таких ошибок можно увидеть в следующем примере:
// Плохой пример! Три ошибки! doSomething().then(function(result) < doSomethingElse(result) // Забыл вернуть промис из внутренней цепочки + неуместное влаживание .then(newResult =>doThirdThing(newResult)); >).then(() => doFourthThing()); // Забыл закончить цепочку методом catch
Первая ошибка это неправильно сцепить вещи между собой. Такое происходит когда мы создаём промис но забываем вернуть его. Как следствие, цепочка сломана, но правильнее было бы сказать что теперь у нас есть две независимые цепочки, соревнующиеся за право разрешится первой. Это означает, что doFourthThing() не будет ждать doSomethingElse() или doThirdThing() пока тот закончится, и будет исполнятся параллельно с ними, это, вероятно, не то что хотел разработчик. Отдельные цепочки также имеют отдельную обработку ошибок, что приводит к необработанным ошибкам.
Вторая ошибка это излишняя вложенность, включая первую ошибку. Вложенность также ограничивает область видимости внутренних обработчиков ошибок, если это не то чего хотел разработчик, это может привести к необработанным ошибкам. Примером этого является пример как не нужно создавать промисы, который комбинирует вложенность с чрезмерным использованием конструктора промисов для оборачивания кода который уже использует промисы.
Третья ошибка это забыть закончить цепочку ключевым словом catch . Незаконченные цепочки приводят к необработанным отторжениям промисов в большинстве браузеров.
Хорошим примером является всегда либо возвращать либо заканчивать цепочки промисов, и как только вы получаете новый промис, возвращайте его сразу же, чтобы не усложнять код излишней вложенностью:
doSomething() .then(function(result) < return doSomethingElse(result); >) .then(newResult => doThirdThing(newResult)) .then(() => doFourthThing()) .catch(error => console.log(error));
Обратите внимание что () => x это сокращённая форма () => < return x; >.
Теперь у нас имеется единственная определённая цепочка с правильной обработкой ошибок.
Использование async / await предотвращает большинство, если не все вышеуказанные ошибки, но взамен появляется другая частая ошибка — забыть ключевое слово await .
Смотрите также
- Promise.then()
- Спецификация Promises/A+ (EN)
- Нолан Лоусон (Nolan Lawson): У нас проблемы с промисами — распространённые ошибки (EN)
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
Как вернуть значение из Promise у Javascript?
У вас всё правильно, кроме того, что вы выводите b синхронно до того, как оно заполнится.
Как только у вас хотя бы в одной функции появляются промисы и асинхронность — забудьте про синхронный код, только .then().
var b; start(343910101) .then(res => ) .then(() => console.log(b));
Ответ написан более трёх лет назад
Нравится 6 4 комментария
krlljs @krlljs Автор вопроса
Алексей Уколов, а если я все же хочу вывести из блока then какие то значения, то как быть?
Super User @sergeystepanov1988
krlljs: если кратко, то никак. Нода асинхронна до самых корней.
krlljs @krlljs Автор вопроса
Super User: у промисов есть this?
Super User @sergeystepanov1988
krlljs: this там ни к чему. Если нужно прокинуть this из внешнего замыкания, то можно воспользоваться стрелочной функцией =>
Максим @maxfarseer
https://maxpfrontend.ru, обучаю реакту и компании
Отличная статья (хабр) на эту тему
Ответ написан более трёх лет назад
Комментировать
Нравится 3 Комментировать
Drugs-driven development
Никак. Промисы не делают код синхронным. Это просто более удобный способ организовать цепочку вызовов асинхронных функций. Получить и использовать значение можно только внутри колбеков (которые внутри then/catch).
Ответ написан более трёх лет назад
Нравится 3 3 комментария
yonen93148 @yonen93148
Полное убожество! И на хрен тогда такая асинхронность нужна?
yonen93148 @yonen93148
Получается больше одного раза вызывать нет смысла. Если код большой, то вообще полная жопа! На кой такое изобретали. Если изначально javascript синхронен, то зачем ставить костыли?
Пример: У меня есть кусок результат выполнения которого должен использоваться в разных местах. Разметить код для 5-10 вызовов довольно больших = киздец! Тем более они в разных местах. Кто-то скажет — асинхронность. Но выполнение такого затормозит так не слабо — идёт выполнение 5-10 кусков одного и того же кода вместо того, чтобы присвоить выполнить один раз, присвоить значение и им пользоваться — что всё преимущества async исчезает на три знакомых всем буквы!
Пример такого кода — генерация подписи и её проверки в разных местах, используя crypto.subtle.xxx (Web Crypto API).
Надо было того придурка, что Promise изобрёл удавить в пелёнках.