Ecmascript 6 promises
Содержание:
- Плюсы и минусы в теории
- Awaiting a promise
- Creating a promise
- How to Use the .then() Promise Handler
- Promise.all
- Rethinking Promise Cancellation
- Новичок на районе: Observables
- Creating a promise: the Promise constructor
- Пример использования
- Promise terminology #
- Dealing with RxJS Observables
- API Reference
- References
- Что такое async/await и promise?
- Promise.allSettled
- Promise.all
- Конструктор Promise, его философия. Callback функция executor — как «выполнитель» обещания. Схема взаимодействия: Promise ( конструктор ) — executor ( callback ) — promise ( объект )
- Концепция 4: Async и await
- Необработанные ошибки
Плюсы и минусы в теории
Async/awaitПлюсы
- Удобство и простота чтения
- Возможность использования последовательного стиля программирования
Минусы
- Легко наткнуться на избыточное ожидание последовательного кода. Для истинной параллельности нужно модифицировать код.
- Неочевидность возвращаемых значений try…catch.
PromiseПлюсы
- Использует традиционный подход колбэков.
- Данные с ошибками и данные с успешным результатом операции однозначно понимаемы.
- Возможность использовать Promise.all без оглядки на синтаксис.
- Оповещения Promise.resolve и Promise.reject доступны везде.
- Наглядное использование метода Promise.finally.
Минусы
При неправильном использовании возможно создание слишком глубоких использований цепочек .then
Awaiting a promise
In order to use a promise, we must somehow be able to wait for it
to be fulfilled or rejected. The way to do this is using
(see warning at the end of this section if
attempting to run these samples).
With this in mind, it’s easy to re-write our earlier
function to use promises:
This still has lots of error handling code (we’ll see how we can
improve on that in the next section) but it’s a lot less error
prone to write, and we no longer have a strange extra parameter.
Non Standard
Note that (used in the examples in
this section) has not been standardised. It is supported by most
major promise libraries though, and is useful both as a teaching
aid and in production code. I recommend using it along with the following polyfill (minified / unminified):
Creating a promise
The Promise API exposes a Promise constructor, which you initialize using :
As you can see, the promise checks the global constant, and if that’s true, the promise goes to a resolved state (since the callback was called); otherwise, the callback is executed, putting the promise in a rejected state. (If none of these functions is called in the execution path, the promise will remain in a pending state)
Using and , we can communicate back to the caller what the resulting promise state was, and what to do with it. In the above case we just returned a string, but it could be an object, or as well. Because we’ve created the promise in the above snippet, it has already started executing. This is important to understand what’s going on in the section below.
A more common example you may come across is a technique called Promisifying. This technique is a way to be able to use a classic JavaScript function that takes a callback, and have it return a promise:
How to Use the .then() Promise Handler
The method should be called on the promise object to handle a result (resolve) or an error (reject).
It accepts two functions as parameters. Usually, the method should be called from the consumer function where you would like to know the outcome of a promise’s execution.
If you are interested only in successful outcomes, you can just pass one argument to it, like this:
If you are interested only in the error outcome, you can pass for the first argument, like this:
However, you can handle errors in a better way using the method that we will see in a minute.
Let’s look at a couple of examples of handling results and errors using the and handlers. We will make this learning a bit more fun with a few real asynchronous requests. We will use the PokeAPI to get information about Pokémon and resolve/reject them using Promises.
First, let us create a generic function that accepts a PokeAPI URL as argument and returns a Promise. If the API call is successful, a resolved promise is returned. A rejected promise is returned for any kind of errors.
We will be using this function in several examples from now on to get a promise and work on it.
Utility method to get a Promise
Example 1: Get 50 Pokémon’s information:
Example 2: Let’s try an invalid URL
Promise.all
Let’s say we want many promises to execute in parallel and wait until all of them are ready.
For instance, download several URLs in parallel and process the content once they are all done.
That’s what is for.
The syntax is:
takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.
The new promise resolves when all listed promises are settled, and the array of their results becomes its result.
For instance, the below settles after 3 seconds, and then its result is an array :
Please note that the order of the resulting array members is the same as in its source promises. Even though the first promise takes the longest time to resolve, it’s still first in the array of results.
A common trick is to map an array of job data into an array of promises, and then wrap that into .
For instance, if we have an array of URLs, we can fetch them all like this:
A bigger example with fetching user information for an array of GitHub users by their names (we could fetch an array of goods by their ids, the logic is identical):
If any of the promises is rejected, the promise returned by immediately rejects with that error.
For instance:
Here the second promise rejects in two seconds. That leads to an immediate rejection of , so executes: the rejection error becomes the outcome of the entire .
In case of an error, other promises are ignored
If one promise rejects, immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.
For example, if there are multiple calls, like in the example above, and one fails, the others will still continue to execute, but won’t watch them anymore. They will probably settle, but their results will be ignored.
does nothing to cancel them, as there’s no concept of “cancellation” in promises. In another chapter we’ll cover that can help with that, but it’s not a part of the Promise API.
allows non-promise “regular” values in
Normally, accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it’s passed to the resulting array “as is”.
For instance, here the results are :
So we are able to pass ready values to where convenient.
Rethinking Promise Cancellation
Generally, I pass all the information the promise needs to determine how to resolve / reject / cancel at promise creation time. That way, there’s no need for a method on a promise. You might be wondering how you could possibly know whether or not you’re going to cancel at promise creation time.
If only there were some kind of object that could stand in for a potential value in the future… oh, wait.
The value we pass in to represent whether or not to cancel could be a promise itself. Here’s how that might look:
Cancellable wait — try it on CodePen
We’re using default parameter assignment to tell it not to cancel by default. That makes the parameter conveniently optional. Then we set the timeout as we did before, but this time we capture the timeout’s ID so that we can clear it later.
We use the method to handle the cancellation and resource cleanup. This will only run if the promise gets cancelled before it has a chance to resolve. If you cancel too late, you’ve missed your chance. That train has left the station.
Новичок на районе: Observables
Перед тем как закончить с промисами, есть кое-что, что пришло для того, чтобы облегчить работу с асинхронными данными — это Observables.
— это ленивые потоки событий, которые могут выдать ноль или больше событий, а могут и вообще не закончиться.
Некоторые ключевые различия между промисами и observables:
Они отменяемые
Они ленивы/медленны
Давайте посмотрим на тоже самое демо, только написанное с помощью . В этом примере, я использую RxJS.
конвертит промис в поток
и среди некоторых операторов доступных для
Потоки ленивы. Наш запускается, когда мы на него.
могут делать много забавных вещей довольно легко. Для примера, delay добавляет функцию за 3 секунды с всего-лишь одной строкой кода или пробовать заново, так что вы можете делать запрос определенное количество раз.
Creating a promise: the Promise constructor
To create a promise in JavaScript, you use the constructor:
The constructor accepts a function as an argument. This function is called the .
The executor accepts two functions with the names, by convention, and .
When you call the , the is called automatically.
Inside the executor, you manually call the function if the executor is completed successfully and invoke the function in case of an error occurs.
If you embed the above JavaScript code in an HTML document and check the console window, you will see that the promise is resolved because the variable is set to .
To see the pending state of the promise, we wrap the code of the executor in the function:
Now, you see that the promise starts with the state with the value is . The promise value will be returned later once the promise is completed.
After about 3 seconds, type the in the console window, you will see that the state of the promise becomes and the promise value is the string that we passed to the function.
So calling the function moves the promise object to the fulfilled state. If you change the value of the variable to and run the script again:
You will see an error message and the state of the promise becomes after 3 seconds:
In other words, calling the method moves the promise object to the state.
The following picture illustrates the states of a promise and the effect of calling the and functions:
Once the promise reaches either fulfilled state or rejected state, it stays in that state and can’t switch.
In other words, a promise cannot go from the fulfilled state to the rejected state and vice versa. It also cannot go back from the fulfilled state or rejected state to the pending state.
If the promise reaches fulfilled state or rejected state, the promise is resolved.
Once a new object is created, it is in the pending state until it is resolved. To schedule a callback when the promise is either resolved or rejected, you call the methods of the object: , , and .
Пример использования
Базовое использования метода
const promise = new Promise(function(resolve, reject) { setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды }); const promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 1000, "promise2"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 1 секунду }); const promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды }); Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания .then(val => console.log(val)); // обработчик для успешного выполнения //
В этом примере мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первом случае, через одну секунду во втором и через пол секунды в третьем.
С использованием метода .all() объекта Promise мы принимаем массив, который содержит ссылку на три обещания, результат выполнения этого метода зависит от того с каким результатом завершится выполнение обещаний, в нашем случае он возвращает объект Promise, который имеет состояние fulfilled (успешное выполнение), так как все переданные объекты Promise в аргументе имеют состояние fulfilled (успешное выполнение).
С использованием метода then() мы добавили обработчик, вызываемый когда объект Promise имеет состояние fulfilled (успешное выполнение), и выводим в консоль полученное значение (массив полученных значений из всех обещаний).
Далее мы с Вами рассмотрим пример в котором увидим, что произойдет, если одно из обещаний будет отклонено:
const promise = new Promise(function(resolve, reject) { setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды }); const promise2 = new Promise(function(resolve, reject) { setTimeout(reject, 1000, new Error("Обещание отклонено")); // изменяем состояние объекта на rejected (выполнение отклонено) через 1 секунду }); const promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды }); Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания .then(val => console.log(val), // обработчик для успешного выполнения err => console.log(err.message)); // обработчик для случая, когда выполнение отклонено // Обещание отклонено
По аналогии с предыдущим примером мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первой и через пол секунды в третьей переменной
Обратите внимание, что с помощью метода reject() мы изменяем значение объекта Promise на rejected (выполнение отклонено) через 1 секунду во второй переменной
С использованием метода .all() объекта Promise мы принимаем массив, который содержит ссылку на три обещания, результат выполнения этого метода зависит от того с каким результатом завершится выполнение обещаний, в нашем случае он возвращает объект Promise, который имеет состояние rejected (выполнение отклонено), это связано с тем, что один из переданных объектов изменил своё состояние на rejected (выполнение отклонено).
С использованием метода then() мы добавили обработчики, вызываемые когда объект Promise имеет состояние fulfilled (успешное выполнение), или rejected (выполнение отклонено). В нашем случае срабатывает обработчик для отклоненного выполнения, и выводит информацию об ошибке в консоль.
Нюансы использования метода
Promise.all() .then(val => console.log(val)); // обработчик для успешного выполнения // Promise.all() .then(val => console.log(val)); // обработчик для успешного выполнения // Promise.all([]) .then(val => console.log(val)); // обработчик для успешного выполнения // []
В этом примере мы рассмотрели основные нюансы использования метода .all(), например, если объект содержит одно, или несколько значений, которые не являются обещаниями, то метод разрешится с этими значениями. Если переданный объект пуст, то возвращенное обещание будет сразу переведено в состояние fulfilled (успешное выполнение).
JavaScript Promise
Promise terminology #
Domenic Denicola proof read the first draft of this article and graded me «F» for terminology. He put me in detention, forced me to copy out States and Fates 100 times, and wrote a worried letter to my parents. Despite that, I still get a lot of the terminology mixed up, but here are the basics:
A promise can be:
- fulfilled — The action relating to the promise succeeded
- rejected — The action relating to the promise failed
- pending — Hasn’t fulfilled or rejected yet
- settled — Has fulfilled or rejected
also uses the term thenable to describe an object that is promise-like, in that it has a method. This term reminds me of ex-England Football Manager Terry Venables so I’ll be using it as little as possible.
Dealing with RxJS Observables
var observable = new Observable(function (subscriber) { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); var promise = Promise.fromObservable(observable); promise.then( function () { console.log('Promise resolved'); }, function (error) { console.log('Promise rejected'); }, function (notification) { console.log(notification); } ); // Will console.log '1', '2', '3' and then 'Promise resolved'
Observable values flow is handled as promise notifications. Promise resolution is triggered by observable completion (therefore with no value) and error stay error regardless the concept Promise/Observable.
NB : If the observable never completes (no call to ), then resulting promise will never resolve.
When you know an observable will return only one value and you want to resolve the promise with it you can set the second parameter of to to apply early resolution :
var observable = SomeAngularService.http.get(someURL); var promise = Promise.fromObservable(observable, true); promise.then(function (data) { console.log('Promise resolved with data from HTTP', data); }, function (error) { console.log('Promise rejected'); });
By using early resolution the promise will be resolved with the first emitted value from the observable.
API Reference
attaches resolution callbacks to a promise and returns a new promise on top of or execution depending on promise final status. The can be used to attach a watcher function on the progress of the promise, see the section.
The new promise returned will be resolved with the return value of the / or be rejected with the value thrown from them. If a object is explicitly returned then the resulting promise will become that returned promise, see the section.
is a shortcut for the only difference is, if is omitted, will still catch the rejection and prevent an exception from rising.
takes an array of promises and returns a new one that will be whether :
- resolved with an array of promises resolution values as soon as they all will be resolved. Resolution values are in the same order as passed in promises.
- rejected with the rejection value of the first rejected promise.
takes an array of promises and returns a new one that will be resolved/rejected the same way the first finishing passed in promise will be.
create a Promise from an RxJS Observable, see the section.
create a promise that resolves with the value optionally provided.
create a promise that rejects with the value optionally provided.
can be called to avoid naming conflicts with others promise libraries (or native object). It returns the object from this library and restores the original global one.
// Load some promise library that exposes Promise (let's name it lib1) // Load Promise.js that also exposes Promise var PromiseJS = Promise.noConflict(); // Promise = lib1 Promise // PromiseJS = Promise.js Promise
️ is only available if Promise.js has been exposed as a global, see the section.
References
- https://promisesaplus.com/
- https://github.com/kriskowal/q
- https://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/
- https://developer.mozilla.org/fr/docs/Web/JavaScript/Concurrence_et_boucle_des_%C3%A9v%C3%A9nements
- https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
- http://voidcanvas.com/setimmediate-vs-nexttick-vs-settimeout/
- https://nodejs.org/api/process.html#process_process_nexttick_callback_args
- https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
- http://stackoverflow.com/q/18826570/1768303
- https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context
- https://www.sitepoint.com/javascript-modules-bundling-transpiling/
Что такое async/await и promise?
Прежде чем ответить на поставленный вопрос, нам необходимо узнать немного теории.
Асинхронность меняет сложившуюся парадигму последовательного кода. Последовательность — когда только одна конкретная операция происходит в данный момент времени. Если функция зависит от результата выполнения другой функции, то она должна дождаться пока прошлая функция не завершит свою работу. Для пользователя это значит состояние вечного «ждуна».
Асинхронность нужна нам, чтобы делать несколько операций и функций параллельно. Асинхронное программирование — это инструмент для оптимизации высоконагруженных сайтов с долгими и частыми ожиданиями обратной связи. Например, когда одна функция создаёт canvas на странице, другая функция может подготовить данные необходимые для отрисовки внутри canvas. Ещё пример, когда пользователь кладет товар в корзину ему не обязательно ждать ответа сервера, мы заранее можем показать анимацию добавления товара в корзину, а всю остальную логику проверок сделать после ответа сервера, не блокируя интерфейс пользователю.
«Летняя ИТ-школа КРОК»
Старт 28 июня, 2 недели, Москва, Беcплатно
tproger.ru
События и курсы на tproger.ru
На самом деле с точки зрения машинного кода, async/await и промисы это абсолютно то же самое. Но мы то с вами люди, и нам важен синтаксис. И разница в синтаксисе настолько существенна, что разделила разработчиков на два лагеря. Любители колбэков выбрали Promise, а не любители цепочек выбрали async/await.
Async/await — синтаксис работающий с промисами, придуман как альтернатива синтаксису промисов. Используя async, можно полностью избежать использования цепочек промисов с помощью await. Async создает Promise. А await ждет выполнения промиса.
Promise — обертка (класс, для простоты понимания) для отложенных и асинхронных вычислений. Ожидает выполнения колбэк функций и никак иначе. Есть два колбэка: один заявляет об успешном выполнении, другой об ошибке. Promise может находиться в трёх состояниях: ожидание (pending), исполнено (fulfilled), отклонено (rejected). Промис начинает выполняться когда мы вызываем метод .
Давайте посмотрим практические маленькие примеры синтаксиса.
Пример 1:
Пример 2:
Пример 3:
Так как является надстройкой над промисами, то мы можем смешивать код, например так:
или так
Promise.allSettled
Новая возможность
Эта возможность была добавлена в язык недавно.
В старых браузерах может понадобиться полифил.
завершается с ошибкой, если она возникает в любом из переданных промисов. Это подходит для ситуаций «всё или ничего», когда нам нужны все результаты для продолжения:
Метод всегда ждёт завершения всех промисов. В массиве результатов будет
- для успешных завершений,
- для ошибок.
Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные.
Используем для этого :
Массив в строке будет таким:
То есть, для каждого промиса у нас есть его статус и значение/ошибка.
Если браузер не поддерживает , для него легко сделать полифил:
В этом коде берёт аргументы, превращает их в промисы (на всякий случай) и добавляет каждому обработчик .
Этот обработчик превращает успешный результат в , а ошибку в . Это как раз и есть формат результатов .
Затем мы можем использовать , чтобы получить результаты всех промисов, даже если при выполнении какого-то возникнет ошибка.
Promise.all
Допустим, нам нужно запустить множество промисов параллельно и дождаться, пока все они выполнятся.
Например, параллельно загрузить несколько файлов и обработать результат, когда он готов.
Для этого как раз и пригодится .
Синтаксис:
Метод принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.
Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.
Например, , представленный ниже, выполнится спустя 3 секунды, его результатом будет массив :
Обратите внимание, что порядок элементов массива в точности соответствует порядку исходных промисов. Даже если первый промис будет выполняться дольше всех, его результат всё равно будет первым в массиве
Часто применяемый трюк – пропустить массив данных через map-функцию, которая для каждого элемента создаст задачу-промис, и затем обернёт получившийся массив в .
Например, если у нас есть массив ссылок, то мы можем загрузить их вот так:
А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же):
Если любой из промисов завершится с ошибкой, то промис, возвращённый , немедленно завершается с этой ошибкой.
Например:
Здесь второй промис завершится с ошибкой через 2 секунды. Это приведёт к немедленной ошибке в , так что выполнится : ошибка этого промиса становится ошибкой всего .
В случае ошибки, остальные результаты игнорируются
Если один промис завершается с ошибкой, то весь завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются.
Например, если сделано несколько вызовов , как в примере выше, и один не прошёл, то остальные будут всё ещё выполняться, но за ними уже не смотрит. Скорее всего, они так или иначе завершатся, но их результаты будут проигнорированы.
ничего не делает для их отмены, так как в промисах вообще нет концепции «отмены». В главе Fetch: прерывание запроса мы рассмотрим , который помогает с этим, но он не является частью Promise API.
разрешает передавать не-промисы в (перебираемом объекте)
Обычно, принимает перебираемый объект промисов (чаще всего массив). Но если любой из этих объектов не является промисом, он передаётся в итоговый массив «как есть».
Например, здесь результат:
Таким образом, мы можем передавать уже готовые значения, которые не являются промисами, в , иногда это бывает удобно.
Конструктор Promise, его философия. Callback функция executor — как «выполнитель» обещания. Схема взаимодействия: Promise ( конструктор ) — executor ( callback ) — promise ( объект )
Итак, мы выяснили, что promise — это сущность, которая технически представляет собой JS объект с особыми скрытыми внутренними полями, которые в свою очередь обеспечивают философское наполнение смыслом слова «обещание».
Когда новичок первый раз создает объект promise, то его ожидает следующая картина (рис. 5).
рис 5. ( Самый первый раз интуитивно создаем promise объект )
Что пошло не так, и почему ошибка — стандартный вопрос. При ответе на него лучше снова привести какую-то аналогию из жизни. Например, мало кто любит «пустозвонов» вокруг нас: которые только обещают, но ничего не делают по выполнению своих заявлений (политика не в счет). Мы намного лучше относимся к тем людям, которые после своего обещания имеют план и предпринимают сразу же какие-то действия для достижения обещанного результата.
Так и философия ECMAScript подразумевает, что если вы создаете обещание, то сразу же укажите, как вы его будете выполнять. Свой план действий программисту необходимо оформить в виде параметра-функции, которую передадите в конструктор Promise. Следующий эксперимент выглядит так (рис. 6).
рис 6. ( Создаем promise объект, передавая в конструктор Promise функцию executor )
Из подписи к рисунку мы видим, что функция (параметр конструктора Promise) имеет собственное название — executor. Её задача — начать выполнение обещания и, желательно, привести его к какому-то логическому завершению. И если программист может писать какой угодно код в executor-е, то как программисту просигнализировать JS-у, что все — работа сделана — можно идти и смотреть результаты обещания?
Маркеры или сигналы, которые помогут программисту сообщить, что обещание завершено, передаются автоматически в параметры executor-a в виде аргументов, специально сформированных JavaScript-ом. Эти параметры можно называть как угодно, но чаще всего вы встретите их под такими именами, как res и rej. В спецификации ECMAScript их полное название — resolve function и reject function. Эти маркеры-функции имеют свои особенности, которые рассмотрим чуть ниже.
Для осознания новой информации новичку предлагается самостоятельно закодировать следующее утверждение: «Обещаю, что смогу разделить одно число на другое и выдать ответ, если только делитель не ноль». Вот как будет выглядеть приблизительно такой код (рис. 7).
рис 7. ( Решение задачи на деление 2-х чисел через промисы )
Теперь можно проанализировать полученный результат. Мы видим, что уже второй раз консоль браузера показывает объект промис в интересном виде. А именно: указаны 2 дополнительных поля в двойных квадратных скобках. Можно спокойно провести аналогию между `PromiseState` и `PromiseStatus`, fulfilled и resolved, `PromiseValue` и `PromiseResult`. Да, браузер сам пытается подсказать программисту о наличии и значении внутренних полей promise объекта. Также мы видим воедино связанную систему объекта promise, функции executor, специальных функций-callback-маркеров res и rej.
Чтобы ученик / напарник раскрепостился еще больше в этом материале, ему предлагается следующий код (рис. 8). Необходимо его проанализировать и ответить на следующие вопросы.
рис 8. ( Вариация решения задачи на деление 2-х чисел через промисы )
Отработает ли код? Где здесь функция executor и какое она имеет имя? Подходящее ли в этом коде название «wantToDivide»? Что возвращает после себя функция bind? Почему в функцию bind аргументы передаются только на втором и третьем месте? Куда исчезли специальные функции resolve function и reject function? Каким образом необходимые вводные данные number1 и number2 попали в «план выполнения обещания»? Сколько элементов в псевдомассиве «arguments»? Можно ли по памяти восстановить то, как будет выглядеть ответ в консоли браузера?
Читателю предлагается самому подумать над ответами на вопросы. А также
поэкспериментировать в коде. Благо код небольшой и сама идея задачи — простая. Да, тут есть вопросы как на промисы, так и на общие знания JavaScript. Что поделать, везде нас поджидают неожиданности, которые не дают нам расслабиться. Как только вам станет все понятно — можно двигаться дальше.
Концепция 4: Async и await
Async-await позволяет вызывать асинхронные функции синхронно. Рассмотрим этот момент более подробно.
Асинхронные функции – это функции, которые переходят в состояние ожидания при вызове, а разрешение зависит от результата выполнения.
Предположим, что нужно обработать две или более подобных функций. После этого результат первой функции необходимо передать второй функции. Для этого можно использовать вторую функцию внутри обратного вызова then первой функции. Но это сделает код более сложным для понимания. Здесь нам на помощь приходит концепция async-await.
Предположим, что у нас есть две асинхронные функции:
- getManagerByEmployeeId: принимает employeeId в качестве входных параметров и возвращает managerId.
- getManagerNameById:принимает в качестве входных параметров managerId и возвращает имя менеджера.
Наша задача – получить имя менеджера для данного идентификатора сотрудника. То есть, реализовать функцию getManagerName в приведенном ниже коде.
const EmployeeIDManagerIdMap = { "AA234": "0AA316", "BBCD5":"4AA354" };const ManagerIdManagerNameMap = { "0AA316":"John Doe", "4AA354":"Ravindram S" };function getManagerByEmployeeId(employeeId) { return new Promise((resolve, reject)=> { setTimeout(()=>{ if(EmployeeIdManagerIdMap) { resolve(EmployeeIdManagerIdMap); } else { reject(`Invalid employee id ${employeeId}`); } },2000); }); }function getManagerNameById(managerId) { return new Promise((resolve, reject)=> { setTimeout(()=>{ if(ManagerIdManagerNameMap) { resolve(ManagerIdManagerNameMap); } else { reject(`Invalid manager id ${managerId}`); } },2000); }); }// получаем имя менеджера по employeeId function getManagerName(employeeId) { // возвращаем имя менеджера }
Один из способов сделать это – использовать структуру вложенных промисов и преобразовать getManagerName в промис.
function getManagerName(employeeId){ return new Promise((resolve, reject)=>{ getManagerByEmployeeId(employeeId).then(function(managerId){ getManagerNameById(managerId).then(function(managerName){ resolve(managerName); }, function(error){ reject(error); }) }, function(error){ reject(error); }) }) }
Это создаст структуру вложенного кода, которая сложна для понимания и заставляет использовать промис в функции getManagerName.
Теперь рассмотрим реализацию концепции async-await.
async function getManagerName(employeeId){ try { let managerId = await getManagerByEmployeeId(employeeId); try { let managerName = await getManagerNameById(managerId); return managerName; } catch(error) { console.error("getManagerNameById promise rejected", error); } } catch(error){ console.error("getManagerByEmployeeId promise rejected", error); } }
Благодаря ей код стал проще и нам не нужно использовать промис в функции getManagerName.
Конструкция await откладывает выполнение кода до тех пор, пока не будет разрешена вызываемая асинхронная функция.
Каждая функция, содержащая await, должна быть объявлена асинхронной с использованием ключевого слова async.
Но отклоненный промис выдает ошибку при вызове функции. Поэтому его нужно обработать с помощью блока try-catch, окружающего вызов асинхронной функции.
Необработанные ошибки
Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить в конец цепочки, как здесь:
В случае ошибки выполнение должно перейти к ближайшему обработчику ошибок. Но в примере выше нет никакого обработчика. Поэтому ошибка как бы «застревает», её некому обработать.
На практике, как и при обычных необработанных ошибках в коде, это означает, что что-то пошло сильно не так.
Что происходит, когда обычная ошибка не перехвачена ? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса.
JavaScript-движок отслеживает такие ситуации и генерирует в этом случае глобальную ошибку. Вы можете увидеть её в консоли, если запустите пример выше.
В браузере мы можем поймать такие ошибки, используя событие :
Это событие является частью .
Если происходит ошибка, и отсутствует её обработчик, то генерируется событие , и соответствующий объект содержит информацию об ошибке.
Обычно такие ошибки неустранимы, поэтому лучше всего – информировать пользователя о проблеме и, возможно, отправить информацию об ошибке на сервер.
В не-браузерных средах, таких как Node.js, есть другие способы отслеживания необработанных ошибок.