Асинхронный JavaScript — Изучение веб-разработки
В этом модуле мы рассмотрим asynchronous JavaScript, почему это важно, и как это поможет эффективно справляться с потенциальной блокировкой операций, таких как получение ресурсов с сервера или запись в файл.
Асинхронный JavaScript довольно сложная тема, и мы советуем пройти Первые шаги в JavaScript и Блоки в JavaScript прежде чем начать эту тему.
Если вы ещё не знакомы с концепциями асинхронного программирования, вам стоит начать со статьи Основные концепции асинхронного программирования в этом модуле. А если уже знакомы, то можете сразу переходить к статье Введение в асинхронный JavaScript.
Примечание: Если вы работаете за компьютером/планшетом/другим устройством где у вас нет возможности создавать собственные файлы, вы можете попробовать(почти все) примеры кода в одном из веб-приложений, таких, как JSBin или Thimble.
- Основные концепции асинхронного программирования
В этой статье мы пройдёмся по нескольким важным концепциям касающихся асинхронного программирования, и того как это выглядит в браузерах и JavaScript.
- Введение в асинхронный JavaScript
- В этой статье мы кратко расскажем о проблемах связанных с синхронным JavaScript-ом, и взглянем на различные техники асинхронного программирования с которыми вы столкнётесь, покажем вам как эти техники помогают решать проблемы синхронного JavaScript.
- Кооперативная асинхронность в JavaScript: Таймауты и интервалы (en-US)
- Здесь мы рассматриваем традиционные методы JavaScript, которые позволяют запускать код асинхронно по истечению заданного времени, или с регулярным интервалом (например: заданное количество раз в секунду), обсудим их пользу, а так же их неотъемлемые проблемы.
- Изящная обработка асинхронных операций с Промисами (en-US)
- Промисы это достаточно новая функция в языке JavaScript, которая позволяет вам откладывать дальнейшие операции, пока предыдущая не выполнится, или реагировать на её неудачное выполнение. Это очень полезно, для установки нужной последовательности операций для корректной работы. Эта статья показывает как работают промисы, и вы рассмотрите то, как они работают в WebAPIs, и узнаете как писать свои собственные.
- Делаем асинхронное программирование проще с async и await
- Промисы могут быть достаточно сложными для написания и понимания, поэтому современные браузеры ввели функцию
async
и операторawait
— где первый позволяет стандартным функциям неявно асинхронно работать с промисами, а последний может использоваться внутриasync
функций, для ожидания промиса, прежде чем функция продолжит свою работу, что делает работу с промисами проще и улучшает читабельность кода. - Выбор правильного подхода (en-US)
- В завершение этого модуля, мы рассмотрим технологии и функции, которые мы обсуждали, рассмотрим когда и где их надо использовать. А так же дадим рекомендации и расскажем о распространённых подводных камнях, там где это будет необходимо.
Основные понятия асинхронного программирования — Изучение веб-разработки
В этой статье мы бегло познакомимся с основными понятиями, связанными с асинхронным программированием и как они применяются в веб браузерах и JavaScript. Вы должны понять эти концепции, прежде чем приступать к другим статьям этого раздела.
Необходимые знания: | Базовая компьютерная грамотность, знакомство с основами JavaScript. |
---|---|
Цель: | Понять основные идеи асинхронного программирования, и как они проявляются в веб-браузерах и JavaScript. |
Как правило, программный код выполняется последовательно, только одна конкретная операция происходит в данный момент времени. Если функция зависит от результата выполнения другой функции, то она должна дождаться пока нужная ей функция не завершит свою работу и не вернёт результат и до тех пор пока это не произойдёт, выполнение программы, по сути, будет остановлено с точки зрения пользователя.
Пользователь современного ПК, наверняка, наблюдал, как курсор меняет свой вид и становится «разноцветным спинером» (у пользователей MacOS). Таким образом операционная система сообщает — «текущая программа, ожидает завершения какого то длительного процесса в системе и я решила сообщить тебе, что бы ты не волновался».
Такое поведение удручает и говорит о неправильном использовании процессорного времени, к тому же современные компьютеры имеют процессоры с несколькими ядрами. Не нужно ничего ждать, вы можете передать следующую задачу свободному ядру процессора и когда она завершится, то сообщит вам об этом. Такой подход позволяет выполнять разные задачи одновременно, в этом и заключается задача асинхронности в программировании. Программная среда, которую вы используете (браузер в случае веб разработки), должна иметь возможность выполнять различного рода задачи асинхронно.
Асинхронные техники очень полезны, особенно при веб разработке. Когда ваше приложение запущено в браузере и выполняет свои задачи, не возвращая контроль окружению, браузер может подвисать. Это называется блокировка; браузер заблокирован и не может реагировать на действия пользователя и выполнять служебные.задачи, до тех пор пока веб приложение не освободит ресурсы процессора.
Давайте рассмотрим несколько примеров, которые покажут, что именно значит блокировка.
В нашем simple-sync.html примере (see it running live), добавим кнопке событие на клик, чтобы при нажатии на неё запускалась трудоёмкая операция (расчёт 10000000 дат, и вывод последней рассчитанной даты на консоль) после чего в DOM добавляется ещё один параграф:
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
let myDate;
for(let i = 0; i < 10000000; i++) {
let date = new Date();
myDate = date
}
console.log(myDate);
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
Когда запустите этот пример, откройте JavaScript консоль и нажмите на кнопку — вы заметите, что параграф не появится на странице, до тех пор пока все даты не будут рассчитаны и результат последнего вычисления не будет выведен на консоль. Этот код выполняется в том порядке, в котором он написан в файле и самая последняя операция не будет запущена, пока не завершатся все операции перед ней.
Примечание: Предыдущий пример слишком не реальный. Вам никогда не понадобится считать столько дат в реальном приложении! Однако, он помогает вам понять основную идею.
В нашем следующем примере, simple-sync-ui-blocking.html (посмотреть пример), мы сделаем что-нибудь более реалистичное, с чем вы сможете столкнуться на реальной странице. Мы заблокируем действия пользователя отрисовкой страницы. В этом примере у нас две кнопки:
- Кнопка «Fill canvas», если на неё кликнуть, рисует в элементе миллион синих кругов.
- Кнопка «Click me for alert», при нажатии показывает предупреждение.
function expensiveOperation() {
for(let i = 0; i < 1000000; i++) {
ctx.fillStyle = 'rgba(0,0,255, 0.2)';
ctx.beginPath();
ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
ctx.fill()
}
}
fillBtn.addEventListener('click', expensiveOperation);
alertBtn.addEventListener('click', () =>
alert('You clicked me!')
);
Если вы быстро нажмёте на первую кнопку и затем быстро кликните на вторую, вы увидите, что предупреждение не появится на странице, пока все круги не будут отрисованы. Первая операция блокирует выполнение следующей до тех пор пока не завершится сама.
Примечание: Хорошо, в приведённом некрасивом примере, мы получили эффект блокировки, который показывает общую проблему при разработке приложений, с которой все время приходится бороться разработчикам.
Почему так происходит? Потому что JavaScript, в общем случае, выполняет команды в одном потоке. Пришло время познакомиться с понятием потока.
Под потоком, обычно, понимают одиночный процесс, который может использовать программа, для выполнения своих нужд. Каждый поток может выполнять только одну в текущий момент времени:
Task A --> Task B --> Task C
Каждая задача будет выполнена последовательно; только когда текущая задача завершится, следующая сможет начаться.
Как мы говорили выше, большинство компьютеров теперь имеют процессор с несколькими ядрами, т.е. могут выполнять несколько задач одновременно. Языки программирования, поддерживающие многопоточность, могут использовать несколько ядер, чтобы выполнять несколько задач одновременно:
Thread 1: Task A --> Task B Thread 2: Task C --> Task D
JavaScript однопоточный
JavaScript, традиционно для скриптовых языков, однопоточный. Даже, если есть несколько ядер, вы можете использовать их только для выполнения задач в одном потоке, называемом основной поток. Наш пример выше, выполняется следующим образом:
Main thread: Render circles to canvas --> Display alert()
В итоге, JavaScript получил несколько инструментов, которые могут помочь в решении подобных проблем. Web workers позволяют вам обработать некоторый JavaScript-код в отдельном потоке, который называется обработчик, таким образом вы можете запускать отдельные блоки JavaScript-кода одновременно. В основном, вы будете использовать воркеры, чтобы запустить ресурсоёмкий процесс, отдельно от основного потока, чтобы не блокировать действия пользователя.
Main thread: Task A --> Task C Worker thread: Expensive task B
Помня об этом, выполните наш следующий пример simple-sync-worker.html (посмотреть пример в действии), с открытой консолью. Это переписанный предыдущий пример, который теперь рассчитывает 10 миллионов дат в отдельном потоке обработчика. Теперь, когда вы нажимаете на кнопку, браузер может добавить новый элемент на страницу, до того как все даты будут посчитаны. Самая первая операция больше не блокирует выполнение следующей.
Воркеры полезный инструмент, но у них есть свои ограничения. Самое существенное, заключается в том, что они не имеют доступа к DOM — вы не можете использовать воркер для обновления UI. Мы не можем отрисовать миллион наших точек внутри воркера; он может только обработать большой объем информации.
Следующая проблема заключается в том, что даже если код запущенный в воркере ничего не блокирует, он в целом остаётся синхронным. Это проблема появляется, когда какой-то функции требуются результаты выполнения нескольких предыдущих функций. Рассмотрим следующую диаграмму потоков:
Main thread: Task A --> Task B
В этом примере, предположим Task A делает что-то вроде получения картинки с сервера а Task B затем делает что-нибудь с полученной картинкой, например, применяет к ней фильтр. Если запустить выполняться Task A и тут же попытаться выполнить Task B, то вы получите ошибку, поскольку картинка ещё не будет доступна.
Main thread: Task A --> Task B --> |Task D| Worker thread: Task C -----------> | |
Теперь, давайте предположим, что Task D использует результат выполнения обеих задач Task B и Task C. Если мы уверенны, что оба результата будут доступны одновременно, тогда не возникнет проблем, однако, часто это не так. Если Task D попытаться запустить, когда какого-то нужного ей результата ещё нет, выполнение закончится ошибкой.
Чтобы избежать подобных проблем, браузеры позволяют нам выполнять определённые операции асинхронно. Такие возможности, как Promises позволяют запустить некоторую операцию (например, получение картинки с сервера), и затем подождать пока операция не вернёт результат, перед тем как начать выполнение другой задачи:
Main thread: Task A Task B Promise: |__async operation__|
Поскольку операция выполняется где-то отдельно, основной поток не блокируется, при выполнении асинхронных задач.
В следующей статье, мы покажем вам, как писать асинхронный код. Захватывает дух, неправда ли? Продолжайте читать!
При проектировании современных программ все больше используется асинхронное программирование, чтобы программа имела возможность выполнять несколько операций в конкретный момент времени. Как только вы начнёте использовать новые, более мощные возможности API, вы обнаружите множество ситуаций, где решить нужную задачу можно только асинхронно. Раньше было сложно писать асинхронный код. До сих пор, нужно время, чтобы привыкнуть к такому подходу, но процесс стал намного легче. Далее, в этом разделе, мы будем глубже исследовать вопрос, когда же асинхронный код необходим и как спроектировать программу, чтобы избежать проблем, описанных выше.
Making asynchronous programming easier with async and await — Изучение веб-разработки
В ECMAScript версии 2017 появились async functions и ключевое слово await
(ECMAScript Next support in Mozilla). По существу, такие функции есть синтаксический сахар над Promises и Generator functions (ts39). С их помощью легче писать/читать асинхронный код, ведь они позволяют использовать привычный синхронный стиль написания. В этой статье мы на базовом уровне разберёмся в их устройстве.
Примечания: | Чтобы лучше понять материал, желательно перед чтением ознакомиться с основами JavaScript, асинхронными операциями вообще и объектами Promises. |
---|---|
Цель материала: | Научить писать современный асинхронный код с использованием Promises и async functions. |
Ключевое слово async
Ключевое слово async позволяет сделать из обычной функции (function declaration или function expression) асинхронную функцию (async function). Такая функция делает две вещи:
— Оборачивает возвращаемое значение в Promise
— Позволяет использовать ключевое слово await (см. дальше)
Попробуйте выполнить в консоли браузера следующий код:
function hello() { return "Hello" };
hello();
Функция возвращает «Hello» — ничего необычного, верно ?
Но что если мы сделаем её асинхронной ? Проверим:
async function hello() { return "Hello" };
hello();
Как было сказано ранее, вызов асинхронной функции возвращает объект Promise.
Вот пример с async function expression:
let hello = async function() { return "Hello" };
hello();
Также можно использовать стрелочные функции:
let hello = async () => { return "Hello" };
Все они в общем случае делают одно и то же.
Чтобы получить значение, которое возвращает Promise, мы как обычно можем использовать метод .then()
:
hello().then((value) => console.log(value))
или ещё короче
hello().then(console.log)
Итак, ключевое слово async
, превращает обычную функцию в асинхронную и результат вызова функции оборачивает в Promise. Также асинхронная функция позволяет использовать в своём теле ключевое слово await, о котором далее.
Ключевое слово await
Асинхронные функции становятся по настоящему мощными, когда вы используете ключевое слово await — по факту, await
работает только в асинхронных функциях. Мы можем использовать await перед promise-based функцией, чтобы остановить поток выполнения и дождаться результата её выполнения (результат Promise). В то же время, остальной код нашего приложения не блокируется и продолжает работать.
Вы можете использовать await
перед любой функцией, что возвращает Promise, включая Browser API функции.
Небольшой пример:
async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);
Конечно, на практике код выше бесполезен, но в учебных целях он иллюстрирует синтаксис асинхронных функций. Теперь давайте перейдём к реальным примерам.
Давайте посмотрим на пример из предыдущей статьи:
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
К этому моменту вы должны понимать как работают Promises, чтобы понять все остальное. Давайте перепишем код используя async/await и оценим разницу.
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
Согласитесь, что код стал короче и понятнее — больше никаких блоков .then()
по всему скрипту!
Так как ключевое слово async
заставляет функцию вернуть Promise, мы можем использовать гибридный подход:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));
Можете попрактиковаться самостоятельно, или запустить наш live example (а также source code).
Минуточку, а как это все работает ?
Вы могли заметить, что мы обернули наш код в функцию и сделали её асинхронной с помощью async
. Это было обязательно — нам надо создать контейнер, внутри которого будет запускаться асинхронный код и будет возможность дождаться его результата с помощью await, не блокируя остальной код нашего скрипта.
Внутри myFetch()
находится код, который слегка напоминает версию на Promise, но есть важные отличия. Вместо того, чтобы писать цепочку блоков .then()
мы просто использует ключевое слово await
перед вызовом promise-based функции и присваиваем результат в переменную. Ключевое слово await
говорит JavaScript runtime приостановить код в этой строке, не блокируя остальной код скрипта за пределами асинхронной функции. Когда вызов promise-based функции будет готов вернуть результат, выполнение продолжится с этой строки дальше.
Пример:
let response = await fetch('coffee.jpg');
Значение Promise, которое вернёт fetch()
будет присвоено переменной response
только тогда, когда оно будет доступно — парсер делает паузу на данной строке дожидаясь этого момента. Как только значение доступно, парсер переходит к следующей строке, в которой создаётся объект Blob
из результата Promise. В этой строке, кстати, также используется await
, потому что метод .blob()
также возвращает Promise. Когда результат готов, мы возвращаем его наружу из myFetch()
.
Обратите внимание, когда мы вызываем myFetch()
, она возвращает Promise, поэтому мы можем вызвать .then()
на результате, чтобы отобразить его на экране.
К этому моменту вы наверное думаете «Это реально круто!», и вы правы — чем меньше блоков .then()
, тем легче читать код.
Добавляем обработку ошибок
Чтобы обработать ошибки у нас есть несколько вариантов
Мы можем использовать синхронную try...catch
структуру с async
/await
. Вот изменённая версия первого примера выше:
async function myFetch() {
try {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
} catch(e) {
console.log(e);
}
}
myFetch();
В блок catch() {}
передаётся объект ошибки, который мы назвали e
; мы можем вывести его в консоль, чтобы посмотреть детали: где и почему возникла ошибка.
Если вы хотите использовать гибридный подходы (пример выше), лучше использовать блок .catch()
после блока .then()
вот так:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch((e) =>
console.log(e)
);
Так лучше, потому что блок .catch()
словит ошибки как из асинхронной функции, так и из Promise. Если бы мы использовали блок try
/catch
, мы бы не словили ошибку, которая произошла в самой myFetch()
функции.
Вы можете посмотреть оба примера на GitHub:
Как вы помните, асинхронные функции построены поверх promises, поэтому они совместимы со всеми возможностями последних. Мы легко можем подождать выполнение Promise.all()
, присвоить результат в переменную и все это сделать используя синхронный стиль. Опять, вернёмся к примеру, рассмотренному в предыдущей статье. Откройте пример в соседней вкладке, чтобы лучше понять разницу.
Версия с async/await (смотрите live demo и source code), сейчас выглядит так:
async function fetchAndDecode(url, type) {
let response = await fetch(url);
let content;
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
content = await response.blob();
} else if(type === 'text') {
content = await response.text();
}
return content;
}
}
async function displayContent() {
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');
let values = await Promise.all([coffee, tea, description]);
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
}
displayContent()
.catch((e) =>
console.log(e)
);
Вы видите, что мы легко изменили fetchAndDecode()
функцию в асинхронный вариант. Взгляните на строку с Promise.all()
:
let values = await Promise.all([coffee, tea, description]);
С помощью await
мы ждём массив результатов всех трёх Promises и присваиваем его в переменную values
. Это асинхронный код, но он написан в синхронном стиле, за счёт чего он гораздо читабельнее.
Мы должны обернуть весь код в синхронную функцию, displayContent()
, и мы не сильно сэкономили на количестве кода, но мы извлекли код блока .then()
, за счёт чего наш код стал гораздо чище.
Для обработки ошибок мы добавили блок .catch()
для функции displayContent()
; Это позволило нам отловить ошибки в обоих функциях.
Примечание: Мы также можем использовать синхронный блок finally
внутри асинхронной функции, вместо асинхронного .finally()
, чтобы получить информацию о результате нашей операции — смотрите в действии в нашем live example (смотрите source code).
Асинхронные функции с async/await бывают очень удобными, но есть несколько замечаний, о которых полезно знать.
Async/await позволяет вам писать код в синхронном стиле. Ключевое слово await
блокирует приостанавливает выполнение ptomise-based функции до того момента, пока promise примет статус fulfilled. Это не блокирует код за пределами вашей асинхронной функции, тем не менее важно помнить, что внутри асинхронной функции поток выполнения блокируется.
ваш код может стать медленнее за счёт большого количества awaited promises, которые идут один за другим. Каждый await
должен дождаться выполнения предыдущего, тогда как на самом деле мы хотим, чтобы наши Promises выполнялись одновременно, как если бы мы не использовали async/await.
Есть подход, который позволяет обойти эту проблему — сохранить все выполняющиеся Promises в переменные, а уже после этого дожидаться (awaiting) их результата. Давайте посмотрим на несколько примеров.
Мы подготовили два примера — slow-async-await.html (см. source code) и fast-async-await.html (см. source code). Они оба начинаются с функции возвращающей promise, имитирующей асинхронность процессов при помощи вызова setTimeout()
:
function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};
Далее в каждом примере есть асинхронная функция timeTest()
ожидающая три вызова timeoutPromise()
:
async function timeTest() {
...
}
В каждом примере функция записывает время начала исполнения и сколько времени понадобилось на исполнение timeTest()
промисов, вычитая время в момент запуска функции из времени в момент разрешения промисов:
let startTime = Date.now();
timeTest().then(() => {
let finishTime = Date.now();
let timeTaken = finishTime - startTime;
alert("Time taken in milliseconds: " + timeTaken);
})
Далее представлена асинхронная функция timeTest()
различная для каждого из примеров.
В случае с медленным примером slow-async-await.html
, timeTest()
выглядит:
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
Здесь мы просто ждём все три timeoutPromise()
напрямую, блокируя выполнение на данного блока на 3 секунды при каждом вызове. Все последующие вызовы вынуждены ждать пока разрешится предыдущий. Если вы запустите первый пример (slow-async-await.html
) вы увидите alert сообщающий время выполнения около 9 секунд.
Во втором fast-async-await.html
примере, функция timeTest()
выглядит как:
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
В данном случае мы храним три объекта Promise
в переменных, каждый из которых может разрешиться независимо от других.
Ниже мы ожидаем разрешения промисов из объекта в результат, так как они были запущенны одновременно, блокируя поток, то и разрешатся одновременно. Если вы запустите второй пример вы увидите alert, сообщающий время выполнения около 3 секунд.
Важно не забывать о быстродействии применяя await, проверяйте количество блокировок.
В качестве последнего замечания, вы можете использовать async
перед методами классов или объектов, вынуждая их возвращать promises. А также await внутри методов объявленных таким образом. Посмотрите на пример ES class code, который мы наблюдали в статье object-oriented JavaScript, и сравните его с модифицированной (асинхронной) async
версией ниже:
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
async greeting() {
return await Promise.resolve(`Hi! I'm ${this.name.first}`);
};
farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
};
}
let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
Первый метод класса теперь можно использовать таким образом:
han.greeting().then(console.log);
One consideration when deciding whether to use async/await is support for older browsers. They are available in modern versions of most browsers, the same as promises; the main support problems come with Internet Explorer and Opera Mini.
If you want to use async/await but are concerned about older browser support, you could consider using the BabelJS library — this allows you to write your applications using the latest JavaScript and let Babel figure out what changes if any are needed for your user’s browsers. On encountering a browser that does not support async/await, Babel’s polyfill can automatically provide fallbacks that work in older browsers.
Вот пожалуй и все — async/await позволяют писать асинхронный код, который легче читать и поддерживать. Даже учитывая, что поддержка со стороны браузеров несколько хуже, чем у promise.then, всё же стоит обратить на него внимание.
Асинхронность Event loop | JavaScript Camp
Асинхронность#
В JavaScript асинхронность — основной инструмент, который обрабатывает запросы параллельно с загрузкой веб-страницы. Сейчас невозможно представить интернет, где все запросы на сервер отправлялись бы с перезагрузкой страницы.
Любые данные от сервера запрашиваются асинхронно: отправляется запрос (XMLHttpRequest или XHR), и код📟 не ждёт его возвращения🔄, продолжая выполняться. Когда же сервер отвечает, объект XHR получает уведомление об этом и запускает функцию⚙️ обратного вызова — callback
, который передали в него перед отправкой запроса.
Если правильно использовать инструменты языка👅, то выполнение запроса, который происходит последовательно и в одном потоке, никак не мешает приёму событий и реакции на них — человек👨 спокойно работает с интерфейсом, не замечая лагов, сбоев и зависаний.
Видео#
Event loop#
Event loop
в JavaScript — менеджер асинхронных вызовов.
Чтобы этот хитрый процесс слаженно работал, в JavaScript реализован механизм для управления очерёдностью исполнения кода📟 . Поскольку это однопоточный язык👅, возникла необходимость «вклиниваться» в текущий контекст исполнения. Этот механизм называется event loop
— событийный цикл.
С английского loop
переводится как «петля», что отлично отражает смысл: мы имеем дело с закольцованной очередью.
Event loop
регулирует последовательность исполнения контекстов — стек. Он формируется, когда сработало событие или была вызвана функция⚙️. Реакция на событие помещается в очередь исполнения, в event loop
, который последовательно, с каждым циклом выполняет попадающий в него код📟 . При этом привязанная к событию функция⚙️ вызывается следующей после текущего контекста исполнения.
В JavaScript постоянно работают связанные между собой синхронная и асинхронная очереди выполнения. Синхронная — stack
— формирует очередь и пробрасывает в асинхронную — event loop
— вызовы функций⚙️, которые будут выполнены после текущего запланированного исполняемого контекста.
Чтобы данные находились в консистентном состоянии, каждая функция⚙️ должна быть выполнена до конца. Это обусловлено однопоточностью JavaScript и некоторыми другими особенностями, например характерными для функциональных ⚙️языков👅 программирования замыканиями. Поэтому единственный поток представлен в виде очереди контекстов исполнения, в которой и происходит «вклинивание» функций⚙️, прошедших через цикл событий.
Описание#
JavaScript это однопоточный язык: одновременно может выполняться только одна задача. Обычно в этом нет ничего сложного, но теперь представьте, что вы запускаете задачу, которая занимает 30 секунд… Да. Во время этой задачи мы ждем 30 секунд, прежде чем что-либо еще может произойти (по умолчанию JavaScript запускается в главном потоке браузера, поэтому весь пользовательский интерфейс будет ждать)😬 Сейчас 2021 год, никто не хочет медленный сайт который тупит.
К счастью, браузер предоставляет нам некоторые функции, которые сам механизм JavaScript не предоставляет: Web API. Который включает в себя DOM API, setTimeout, HTTP-запросы и так далее. Это может помочь нам создать асинхронное неблокирующее поведение 🚀.
Когда мы вызываем функцию, она добавляется в call stack(стек вызовов). Стек вызовов является частью механизма JS, это не зависит от браузера. Это классический взгляд на стек, т.е first in
, last out
. Когда функция возвращает значение, она «выталкивается» из стека.
function great() { return 'Hello'}
function respond() { return setTimeout(() => alert('Hey!'), 1000)}
great()respond()
СкопироватьФункция respond
возвращает функцию setTimeout
. SetTimeout
предоставляется нам через Web-API
: он позволяет нам делить задачи, не блокируя основной поток. Callback
функция, которую мы передали в функцию setTimeout
, лямбда функция () => {return 'Hey'}
добавляется в Web-API
. Тем временем setTimeout
и responde
извлекаются из стека и возвращают свои значения.
В Web-API
таймер работает до тех пор, пока второй аргумент, который мы передали ему, не подождет 1000 мс. Callback
не сразу добавляется в стек вызовов, а передается в нечто, называемое очередью.
Это может сбивать с толку: это не означает, что callback
функия добавляется в стек вызовов (таким образом, возвращает значение) через 1000 мс! Он просто добавляется в очередь через 1000 мс. Но в этой очереди, функция должна ждать пока придет ее черёд.
Теперь это та часть, которую мы все ждали… Время для event loop
выполнить единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, то есть, если все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент в очереди добавляется в стек вызовов. В этом случае никакие другие функции не были вызваны, что означает, что стек вызовов был пуст к тому времени, когда callback
функция была первым элементом в очереди.
callback
добавляется в стек вызовов, вызывается и возвращает значение, а также извлекается из стека.
Смотреть весело, но вы не сможете полностью понять тему, не работая с ней снова и снова. Попробуйте выяснить, что появится в консоли, если мы запустим следующее:
const foo = () => console.log('First')const bar = () => setTimeout(() => console.log('Second'), 500)const baz = () => console.log('Third')
bar()foo()baz()
СкопироватьДавайте посмотрим, что происходит, когда мы запускаем этот код в браузере:
Мы вызываем bar
, которая возвращает функцию setTimeout
. Callback
который мы передали в setTimeout
добавляется в Web API
, функция setTimeout
и bar
извлекаются из стека вызовов.
Таймер запускается, тем временем foo
вызывается и записывает в журнал First
. foo
возвращает undefined
, baz
вызывается и callback
добавляется в очередь baz
логирует Third
. Цикл обработки событий видит, что коллстек пуст после возврата baz
, после чего колбэк добавляется в стек вызовов. Callback
логирует Second
.
Надеюсь, что это заставит вас чувствовать себя более уверено с циклом событий event loop
!
Не беспокойтесь, если это все еще кажется запутанным, самое важное — понять, откуда могут возникнуть определенные ошибки или специфическое поведение.
Проблемы?#
Пишите в Discord или телеграмм чат, а также подписывайтесь на наши новости
Вопросы:#
Асинхронность — это:
- Инструмент, который выводит контекст исполнения функции из синхронного потока
- Инструмент, который исполняет код построчно
- Инструмент, который обрабатывает запросы параллельно с загрузкой веб-страниц
Менеджер асинхронных вызовов:
stack
Event loop
Объекты высшего класса
Инструмент, выполняющий код с задержкой в миллисекундах:
delay
heap
setTimeout
Для того чтобы понять, на сколько вы усвоили этот урок, пройдите тест в мобильном приложении нашей школы по этой теме или в нашем телеграм боте.
Ссылки:#
- Объяснение работы EventLoop в JavaScript
- Как управлять event loop в JavaScript
- Справочник javascript
- Статья: Объяснение Event Loop в Javascript с помощью визуализации
- Статья: JavaScript Visualized: Promises & Async/Await
Contributors ✨#
Thanks goes to these wonderful people (emoji key):
Асинхронность в javascript
Асинхронность в javascript — представляет собой правило в соответствии с которым блоки JS кода обрабатываются браузером параллельно с загрузкой DOM — т.е. структуры веб-страницы или после загрузки DOM.
Код на JS, как известно, помещается между тегами <script> </script>. При этом код может содержаться как в HEAD документа, так и в BODY — при этом в любой части документа. Также javascript можно подгружать с других доменов.
Обычно JS помещают в конце страницы именно из тех соображений, что на обработку кода уходит определенное время — загрузка страницы при этом приостанавливается — DOM не может загрузиться поскольку ждет завершения исполнения скритпа на javascript.
Если при этом скрипт написан с ошибками или подгружается с внешнего сервера, который оказался недоступен — страница не загрузится никогда. Если JS код просто плохо оптимизирован и выполняется долго — страница загрузится до того места в котором подключается скрипт — затем загрузка остановится до того момента пока выполнение скрипта не завершится. Это может быть замечено посетителем сайта, особенно если он использует медленное соединение с Интернетом.
Чаще всего посетитель с сайта, страница которого перестала загружаться на половине уйдет не дождавшись пока скрипт отработает до конца. Задержки, таким образом, очень нежелательны.
Чтобы исправить эту ситуацию предусмотрена опция async, позволяющая загружать JS асинхронно. Структура документа при использовании документа и все элементы страницы не будут ждать завершения исполнения скрипта.
Скрипт инициируется, страница продолжит загружаться даже если скрипт не завершил свою работу. Какой-то функционал страницы при этом может работать некорректно (если в javascript допущены ошибки) — в целом же страница загружается и посетитель сайта может с ней работать.
Стоит отметить, что async не поддерживается некоторыми версиями Internet Explorer, но поскольку пользуется им очень маленькое количество пользователей — обычно имеет смысл этим пренебречь и использовать асинхронность в javascript.
Альтернативой async может быть defer. При использовании данного атрибута скрипт будет исполняться только после того как загружен DOM. Директива поддерживается всеми браузерами, ее особенность в том, что она обрабатывает JS скрипты в том порядке, в котором они подключаются.
Это может быть как плюсом, так и минусом. Если порядок выполнения скриптов принципиален — defer — лучшее решение.async можно принудительно отключить: script.async=false;
Минус в том, что если один скрипт, подключается с внешнего ресурса, который недоступен — остальные скрипты отрабатывать не будут. Они будут ждать этот независимо от того насколько он важен в целом.
Дайджест 4Front Meetup #6: Docker, Yeoman Generator, асинхронность в JavaScript и др.
Представляем вашему вниманию очередной дайджест с видеозаписями и слайдами лекций с весеннего 4front митапа #6, который был проведен 12 марта 2015 г. компанией XB Software в бизнес-клубе IMAGURU.
В этот раз участники мероприятия поделились своим опытом в использовании Docker, создании собственных yeoman-генераторов, а также поведали о том, как применять мультипоточность в полевых условиях и как бороться с асинхронностью в JavaScript.
Смотрите также видеозаписи, презентации и множество различных демок с пяти прошлогодних 4front митапов здесь. А также рекомендуем посмотреть видеодайджест с лекциями разработчиков, выступавших на первом правильном хакатоне для разработчиков What The Hack 2014.
Все видео и презентации c 4Front митапа #6:
1.Docker в хозяйстве и быту
Веб-разработчик XB Software Павел Клименков рассказал про то, что такое Docker, зачем он нужен, что общего у докера с Vagrant, Git и гипервизорами, и поделился примерами его использования.
Презентация:
Видео:
2.Yeoman Generator своими руками
Фрилансер и фул-стек разработчик Борис Мосунов поделился на 4front meetup #6 тем, что такое Yeoman генераторы, зачем они нужны и как создать свой собственный Yeoman генератор с нуля.
Презентация:
Видео:
3.Параллельность в JavaScript
Веб-разработчик XB Software Алексей Саскевич рассказал о многопоточности в современном JavaScript, поделился приемами и советами, а также рассказал, как на практике используются веб-воркеры и как применяются библиотеки parallel.js и multithread.js, помогающие решить различные проблемы с многозадачностью.
Презентация:
Видео:
4.Борьба с асинхронностью в JavaScript
Про старый добрый Eventloop, Promise и готовые решения для Promise, шаблоны асинхронного программирования, а также ES6 и его генераторы поведал постоянный участник всех наших митапов и хакатонов веб-разработчик XB Software Владимир Дашукевич.
Презентацию в слайдах смотрите здесь.
Видео:
Подписывайтесь на нашу рассылку и получайте новые дайджесты от XB Software.
The following two tabs change content below.Маркетолог XB Software с большим опытом в области интернет-маркетинга. Увлекается юзабилити и стремится создавать полезный контент, отвечающий интересам ИТ-аудитории.
JavaScript, часть 2: прототипы и асинхронность. Курс от Coursera: обзор, отзывы, аналоги, интеграция, сайт
Описание
- Формат — Онлайн-курс
- Длительность — 6 недель
- Стоимость — Бесплатно
Этот курс продолжает обучение тех, кто уже изучил основы JavaScript. На очереди не самые простые вещи: прототипы, конструкторы, асинхронный код, Node.js и DOM. По окончании обучения вы будете уметь программировать на JavaScript.
Программа курса
- 1 НЕДЕЛЯ. Прототипы. 7 видео ((всего 25 мин.)), 5 материалов для самостоятельного изучения, 1 тест
- 2 НЕДЕЛЯ. Конструкторы. 7 видео ((всего 36 мин.)), 6 материалов для самостоятельного изучения, 2 тестов
- 3 НЕДЕЛЯ. Асинхронный код. 6 видео ((всего 26 мин.)), 3 материалов для самостоятельного изучения, 1 тест
- 4 НЕДЕЛЯ. Node.js. 7 видео ((всего 50 мин.)), 3 материалов для самостоятельного изучения, 1 тест
- 5 НЕДЕЛЯ. DOM. 4 видео ((всего 35 мин.)), 7 материалов для самостоятельного изучения, 1 тест
Аналоги и альтернативы для JavaScript, часть 2: прототипы и асинхронность. Курс от Coursera
JavaScript, часть 2: прототипы и асинхронность. Курс от Coursera — похожие решения и продукты
Комплексное обучение JavaScript. Курс от LoftSchool
Интенсивный курс рассчитан на веб-разработчиков с опытом от 1 года или на тех, кто уже прошел наш курс «Веб-разработка для начинающих»
JavaScript-фреймворк React.js. Курс от Skillbox
Расширьте свои профессиональные знания и навыки разработчика, научившись использовать в работе фреймворк React.js
Как стать React-разработчиком. Курс от Яндекс.Практикум
Курс подойдет тем, кто умеет верстать и знает основы JavaScript
Профессия Frontend-разработчик PRO. Курс от Skillbox
Вы начнёте с основ вёрстки и JavaScript, а к концу обучения научитесь делать корпоративные сервисы. Получите опыт работы в команде и начнёте карьеру веб-разработчика
Специализация Frontend-разработчик. Курс от SkillFactory
Получите перспективную творческую профессию в IT
Факультет frontend-разработки. Курс от GeekBrains
Освойте современную профессию: вы научитесь создавать сайты и приложения, проектировать интерфейсы и работать со сложными инструментами frontend-разработчика
Frontend-разработчик с нуля. Курс от Нетология
Соберите крутое портфолио из 9 жизнеспособных проектов для получения работы своей мечты
Frontend-разработчик. Курс от Skillbox
Вы изучите основы HTML, CSS и JavaScript, научитесь создавать сайты и приложения для любых устройств, оптимизировать код и работать с анимацией
JavaScript, часть 1: основы и функции. Курс от Coursera
Авторы курса — разработчики из Яндекса
Angular. Курс от Skillbox
Вы научитесь создавать веб-приложения, используя популярный фреймворк Angular. Освоите лучшие практики и продвинутые подходы разработки приложений, продвинетесь по карьерной лестнице и сможете зарабатывать больше
Автоматизированное тестирование веб-приложений на JavaScript. Курс от Skillbox
Вы научитесь программировать на JavaScript, работать с фреймворками Selenium Webdriver и Cypress, тестировать пользовательские интерфейсы и настраивать CI. Соберёте портфолио, сможете претендовать на повышение или работу в крупной IT-компании
React.js Разработка веб-приложений. Курс от LoftSchool
Курс рассчитан на веб-разработчиков с опытом от 1 года
React: библиотека фронтенд-разработки №1. Курс от Нетология
Создайте более 20 вариантов интерактивных интерфейсов во время обучения
Профессия Fullstack-разработчик на JavaScript. Курс от Skillbox
Вы с нуля научитесь разрабатывать полноценные сайты и веб-приложения на JS и изучите один из фреймворков — Vue, React или Angular. Станете ценным сотрудником для любой IT-компании, поймёте, как получить повышение, и сможете зарабатывать больше
Node.js Серверный JavaScript. Курс от LoftSchool
Курс рассчитан на веб-разработчиков с опытом разработки на языке JavaScript и на выпускников курсов «Vue.js Продвинутая веб-разработка» или «Комплексное обучение JavaScript»
Асинхронный JavaScript — Изучите веб-разработку
В этом модуле мы рассмотрим асинхронный JavaScript, почему он важен и как его можно использовать для эффективной обработки потенциальных блокирующих операций, таких как выборка ресурсов с сервера.
Хотите стать интерфейсным веб-сайтом разработчик?
Мы составили курс, который включает в себя всю важную информацию, необходимую для работать для достижения своей цели.
Начать
- Общие концепции асинхронного программирования
В этой статье мы рассмотрим ряд важных концепций, касающихся асинхронного программирования, и того, как это выглядит в веб-браузерах и JavaScript.Вы должны понять эти концепции, прежде чем работать с другими статьями модуля.
- Введение в асинхронный JavaScript
- В этой статье мы кратко рассмотрим проблемы, связанные с синхронным JavaScript, и сначала рассмотрим некоторые из различных методов асинхронного JavaScript, с которыми вы столкнетесь, показывая, как они могут помочь нам решить такие проблемы.
- Кооперативный асинхронный JavaScript: таймауты и интервалы
- Здесь мы рассмотрим традиционные методы, доступные в JavaScript для асинхронного выполнения кода по истечении заданного периода времени или с регулярным интервалом (например.грамм. определенное количество раз в секунду), поговорите о том, для чего они полезны, и посмотрите на присущие им проблемы.
- Изящная обработка асинхронных операций с помощью обещаний
- Promises — это сравнительно новая функция языка JavaScript, позволяющая отложить дальнейшие действия до завершения предыдущего действия или отреагировать на его сбой. Это действительно полезно для настройки правильной работы последовательности операций. В этой статье показано, как работают обещания, где вы увидите их использование в WebAPI и как написать свои собственные.
- Упрощение асинхронного программирования с помощью async и await
- Promises могут быть довольно сложными для настройки и понимания, поэтому современные браузеры реализовали
async
функций иawait
оператор. Первый позволяет стандартным функциям неявно вести себя асинхронно с обещаниями, тогда как последний может использоваться внутри функцийasync
для ожидания обещаний перед продолжением функции. Это упрощает цепочку обещаний и облегчает их чтение. - Выбор правильного подхода
- В завершение этого модуля мы рассмотрим различные методы и функции кодирования, которые мы обсуждали на протяжении всего, и посмотрим, какие из них вам следует использовать, когда, с рекомендациями и напоминаниями об общих ловушках, где это уместно.
Изящное асинхронное программирование с помощью Promises — изучение веб-разработки
Обещания — это сравнительно новая функция языка JavaScript, позволяющая отложить дальнейшие действия до тех пор, пока не будет выполнено предыдущее действие, или отреагировать на его сбой.Это полезно для настройки последовательности асинхронных операций для правильной работы. В этой статье показано, как работают обещания, как вы увидите их использование с веб-API и как написать свои собственные.
Предварительные требования: | Базовая компьютерная грамотность, хорошее понимание основ JavaScript. |
---|---|
Цель: | Чтобы понять обещания и как их использовать. |
Мы кратко рассмотрели обещания в первой статье курса, но здесь мы рассмотрим их гораздо глубже.
По сути, Promise — это объект, который представляет промежуточное состояние операции — по сути, обещание о том, что какой-то результат будет возвращен в какой-то момент в будущем. Нет гарантии того, когда именно операция будет завершена и результат будет возвращен, но — это гарантия того, что, когда результат доступен или обещание не выполняется, предоставленный вами код будет выполнен, чтобы сделать что-то еще. с успешным результатом или аккуратно обработать случай сбоя.
Как правило, вас меньше интересует количество времени, которое потребуется асинхронной операции, чтобы вернуть свой результат (если, конечно, это не занимает далеко, слишком долго!), И вас больше интересует возможность ответить на его возвращаемый результат всякий раз, когда то есть. И, конечно, приятно, что он не блокирует выполнение остальной части кода.
Одно из наиболее частых случаев использования обещаний — это веб-API, возвращающие обещание. Рассмотрим гипотетическое приложение для видеочата.В приложении есть окно со списком друзей пользователя, и нажатие кнопки рядом с пользователем запускает видеозвонок этому пользователю.
Обработчик этой кнопки вызывает getUserMedia ()
, чтобы получить доступ к камере и микрофону пользователя. Поскольку getUserMedia ()
должен гарантировать, что у пользователя есть разрешение на использование этих устройств, и спрашивают пользователя, какой микрофон использовать и какую камеру использовать (или будет ли вызов только голосовым, среди других возможных вариантов), он может блокироваться до тех пор, пока не будут приняты не только все эти решения, но также не будут задействованы камера и микрофон.Кроме того, пользователь может не сразу отвечать на эти запросы разрешений. Это может занять много времени.
Поскольку вызов getUserMedia ()
выполняется из основного потока браузера, весь браузер блокируется до тех пор, пока getUserMedia ()
не вернется! Очевидно, это неприемлемый вариант; без обещаний все в браузере становится непригодным для использования, пока пользователь не решит, что делать с камерой и микрофоном. Таким образом, вместо ожидания пользователя, включения выбранных устройств и прямого возврата MediaStream
для потока, созданного из выбранных источников, getUserMedia ()
возвращает обещание
, которое разрешается с помощью MediaStream
, как только оно доступный.
Код, который будет использовать приложение для видеочата, может выглядеть примерно так:
function handleCallButton (evt) {
setStatusMessage ("Звонок ...");
navigator.mediaDevices.getUserMedia ({video: true, audio: true})
.then (chatStream => {
selfViewElem.srcObject = chatStream;
chatStream.getTracks (). forEach (track => myPeerConnection.addTrack (track, chatStream));
setStatusMessage («Подключено»);
}). catch (err => {
setStatusMessage («Не удалось подключиться»);
});
}
Эта функция начинается с использования функции с именем setStatusMessage ()
для обновления отображения статуса сообщением «Вызов… «, указывая на то, что выполняется попытка вызова. Затем он вызывает getUserMedia ()
, запрашивая поток, который имеет как видео, так и аудиодорожки, а затем, как только он был получен, настраивает элемент видео, чтобы показать поток, исходящий из камера как «собственное изображение», затем берет каждую из дорожек потока и добавляет их в WebRTC RTCPeerConnection
, представляющий соединение с другим пользователем. После этого на дисплее состояния отображается «Подключено».
В случае сбоя getUserMedia ()
запускается блок catch
.При этом используется setStatusMessage ()
для обновления окна состояния, чтобы указать, что произошла ошибка.
Здесь важно то, что вызов getUserMedia ()
возвращается почти сразу, даже если поток камеры еще не получен. Даже если функция handleCallButton ()
уже вернулась к коду, который ее вызвал, по завершении работы getUserMedia ()
она вызывает предоставленный вами обработчик. Пока приложение не предполагает, что потоковая передача началась, оно может просто продолжать работать.
Примечание: Вы можете узнать больше об этой несколько сложной теме, если вам интересно, в статье Сигнализация и видеозвонки. В этом примере используется код, похожий на этот, но гораздо более полный.
Чтобы полностью понять, почему обещания — это хорошо, полезно вспомнить старые обратные вызовы и понять, почему они проблематичны.
Давайте поговорим о заказе пиццы по аналогии. Есть определенные шаги, которые вы должны предпринять для того, чтобы ваш заказ был успешным, что на самом деле не имеет смысла пытаться выполнить не по порядку или по порядку, но до того, как каждый предыдущий шаг будет полностью завершен:
- Вы сами выбираете, какую начинку хотите.Это может занять некоторое время, если вы нерешительны, и может потерпеть неудачу, если вы просто не можете принять решение или вместо этого решите купить карри.
- Затем вы размещаете свой заказ. Это может занять некоторое время, чтобы вернуть пиццу, и может не получиться, если в ресторане нет необходимых ингредиентов для ее приготовления.
- Затем вы собираете пиццу и едите. Это может потерпеть неудачу, если, скажем, вы забыли свой кошелек и не можете заплатить за пиццу!
При использовании обратных вызовов в старом стиле псевдокодовое представление вышеуказанной функциональности может выглядеть примерно так:
chooseToppings (функция (начинки) {
placeOrder (начинки, функция (порядок) {
collectOrder (заказ, функция (пицца) {
eatPizza (пицца);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Это запутанный и трудный для чтения (часто называемый «ад обратного вызова»), требует многократного вызова failureCallback ()
(один раз для каждой вложенной функции), помимо прочего.
Улучшения с обещаниями
Обещания значительно упрощают написание, синтаксический анализ и выполнение ситуаций, подобных описанным выше. Если бы мы представили вышеупомянутый псевдокод, используя вместо этого асинхронные обещания, мы бы получили что-то вроде этого:
выбрать топпинги ()
.then (функция (начинки) {
return placeOrder (начинки);
})
.then (функция (порядок) {
return collectOrder (заказ);
})
.then (функция (пицца) {
eatPizza (пицца);
})
.catch (failureCallback);
Это намного лучше — легче увидеть, что происходит, нам нужен только один .catch ()
для обработки всех ошибок, он не блокирует основной поток (поэтому мы можем продолжать играть в видеоигры, пока ждем, пока пицца будет готова к сбору), и каждая операция гарантированно ожидает выполнения предыдущих операций выполнить перед запуском. Таким образом, мы можем связать несколько асинхронных действий друг за другом, потому что каждый блок .then ()
возвращает новое обещание, которое разрешается, когда блок .then ()
завершает работу. Умно, правда?
Используя стрелочные функции, вы можете еще больше упростить код:
выбрать топпинги ()
.затем (начинки =>
placeOrder (начинки)
)
.then (заказ =>
collectOrder (заказ)
)
.then (пицца =>
eatPizza (пицца)
)
.catch (failureCallback);
Или даже так:
выбрать топпинги ()
.then (начинки => placeOrder (начинки))
.then (заказ => collectOrder (заказ))
.then (pizza => eatPizza (пицца))
.catch (failureCallback);
Это работает, потому что со стрелочными функциями () => x
является допустимым сокращением для () => {return x; }
.
Вы даже можете сделать это, поскольку функции просто передают свои аргументы напрямую, поэтому нет необходимости в этом дополнительном уровне функций:
chooseToppings (). Then (placeOrder) .then (collectOrder) .then (eatPizza) .catch (failureCallback);
Однако это не так легко читать, и этот синтаксис может оказаться непригодным для использования, если ваши блоки более сложны, чем то, что мы показали здесь.
Примечание: Вы можете сделать дальнейшие улучшения с синтаксисом async
/ await
, о котором мы поговорим в следующей статье.
По своей сути обещания похожи на прослушиватели событий, но с некоторыми отличиями:
- Обещание может быть успешным или неудачным только один раз. Он не может быть успешным или неудачным дважды, и он не может переключаться с успеха на неудачу или наоборот после завершения операции.
- Если обещание выполнено успешно или не выполнено, и вы позже добавите обратный вызов успеха / неудачи, будет вызван правильный обратный вызов, даже если событие произошло раньше.
Обещания важно понимать, потому что большинство современных веб-API используют их для функций, которые выполняют потенциально длительные задачи.Чтобы использовать современные веб-технологии, вам нужно использовать обещания. Позже в этой главе мы рассмотрим, как написать собственное обещание, а пока мы рассмотрим несколько простых примеров, с которыми вы столкнетесь в веб-API.
В первом примере мы будем использовать метод fetch ()
для извлечения изображения из Интернета, метод Response.blob ()
для преобразования необработанного содержимого тела ответа выборки в объект Blob
и затем отобразите этот blob внутри элемента
.Это очень похоже на пример, который мы рассмотрели в первой статье этой серии, но мы сделаем это немного по-другому, поскольку мы заставим вас создавать собственный код, основанный на обещаниях.
Примечание: Следующий пример не будет работать, если вы просто запустите его непосредственно из файла (то есть через URL-адрес file: //
). Вам необходимо запустить его через локальный сервер тестирования или использовать онлайн-решение, такое как страницы Glitch или GitHub.
Прежде всего, загрузите наш простой шаблон HTML и образец файла изображения, который мы получим.
Добавьте элемент
.
Внутри элемента
добавьте следующую строку:
let обещание=выборка('coffee.jpg');
Это вызывает метод
fetch()
,передавая ему URL-адрес изображения для выборки из сети в качестве параметра.Это также может принимать объект параметров как необязательный второй параметр,но пока мы используем самую простую версию.Мы сохраняем объект обещания,возвращаемый функциейfetch()
,внутри переменной с именемPromise
.Как мы уже говорили,этот объект представляет собой промежуточное состояние,которое изначально не является ни успехом,ни неудачей-официальный термин для обещания в этом состоянии-в ожидании.Чтобы ответить на успешное завершение операции всякий раз,когда это происходит(в данном случае,когда возвращается
Response
),мы вызываем метод.then()
объекта обещания.Обратный вызов внутри блока.then()
выполняется только тогда,когда вызов обещания завершается успешно и возвращает объектResponse
-в разговоре обещаний,когда он был выполнен.Ему передается возвращенный объектResponse
в качестве параметра.Примечание:Блок
.then()
работает аналогично тому,как вы добавляете прослушиватель событий к объекту с помощьюAddEventListener()
.Он не запускается,пока не произойдет событие(когда обещание выполнено).Наиболее заметным отличием является то,что.then()
будет запускаться только один раз при каждом использовании,тогда как прослушиватель событий может быть вызван несколько раз.Мы немедленно запускаем метод
blob()
для этого ответа,чтобы гарантировать,что тело ответа полностью загружено,и когда оно станет доступным,преобразуем его в объектBlob
,с которым мы можем что-то сделать.Результат возвращается так:response=>response.blob()
,что является сокращением для
функция(ответ){ответный ответ.blob();}
К сожалению,нам нужно сделать немного больше,чем это.Обещания Fetch не терпят неудачу при ошибках 404 или 500-только при таких катастрофических событиях,как сбой сети.Вместо этого они преуспевают,но для свойства
response.ok
установлено значениеfalse
.Например,чтобы вызвать ошибку в 404,нам нужно проверить значениеresponse.ok
,и еслиfalse
,выдать ошибку,возвращая blob только в том случае,еслиtrue
.Это можно сделать так-добавьте следующие строки под первой строкой JavaScript.let обещание2=обещание.then(response=>{if(!response.ok){выдать новую ошибку(`Ошибка HTTP! status: $ {response.status}`);}еще{вернуть response.blob();}});
Каждый вызов
.then()
создает новое обещание.Это очень полезно;поскольку методblob()
также возвращает обещание,мы можем обработать объектBlob
,который он возвращает при выполнении,вызвав метод.then()
второго обещания.Поскольку мы хотим сделать с большим двоичным объектом что-то более сложное,чем просто запустить для него один метод и вернуть результат,на этот раз нам нужно заключить тело функции в фигурные скобки(иначе это приведет к ошибке).Добавьте в конец кода следующее:
let обещание3=обещание2.then(myBlob=>{});
Теперь заполним тело обратного вызова
.then()
.Добавьте следующие строки в фигурные скобки:пусть objectURL=URL.createObjectURL(myBlob);пусть изображение=документ.createElement('img');image.src=objectURL;document.body.appendChild(изображение);
Здесь мы запускаем метод
URL.createObjectURL()
,передавая его в качестве параметра,который объектBlob
возвращает,когда выполняется второе обещание.Это вернет URL-адрес,указывающий на объект.Затем мы создаем элемент
,устанавливаем его атрибутsrc
равным URL-адресу объекта и добавляем его в DOM,чтобы изображение отображалось на странице!
Если вы сохраните только что созданный HTML-файл и загрузите его в свой браузер,вы увидите,что изображение отображается на странице должным образом.Хорошая работа!
Примечание:Вы,наверное,заметили,что эти примеры несколько надуманы.Вы можете просто покончить со всей цепочкойfetch()
иblob()
и просто создать элемент
и установить для его значения атрибутаsrc
URL-адрес файла изображения,coffee.jpg
.Однако мы выбрали этот пример,потому что он демонстрирует обещания в простой и приятной форме,а не из-за того,что он уместен в реальном мире.
Реагирование на сбой
Что-то отсутствует-в настоящее время нет ничего,что могло бы явным образом обрабатывать ошибки,если одно из обещаний терпит неудачу(отклоняет,говоря языком обещаний).Мы можем добавить обработку ошибок,запустив метод.catch()
из предыдущего обещания.Добавьте это сейчас:
let errorCase=Promise3.catch(e=>{console.log('Возникла проблема с вашей операцией выборки:'+e.message);});
Чтобы увидеть это в действии,попробуйте неправильно написать URL-адрес изображения и перезагрузить страницу.Об ошибке будет сообщено в консоли инструментов разработчика вашего браузера.
Это не намного больше,чем если бы вы вообще не позаботились о включении блока.catch()
,но подумайте об этом-это позволяет нам контролировать обработку ошибок именно так,как мы хотим.В реальном приложении ваш блок.catch()
может повторить попытку получения изображения,или показать изображение по умолчанию,или предложить пользователю предоставить другой URL-адрес изображения или что-то еще.
Объединение блоков в цепочку
Это очень длинный способ записать это;мы намеренно сделали это,чтобы помочь вам четко понять,что происходит.Как было показано ранее в статье,вы можете объединить в цепочку блоки.then()
(а также блоки.catch()
).Приведенный выше код также можно было бы написать так(см.Также simple-fetch-chained.html на GitHub):
fetch('coffee.jpg').then(response=>{if(!response.ok){выдать новую ошибку(`Ошибка HTTP! status: $ {response.status}`);}еще{вернуть response.blob();}}).then(myBlob=>{пусть objectURL=URL.createObjectURL(myBlob);пусть изображение=документ.createElement('img');image.src=objectURL;document.body.appendChild(изображение);}).catch(e=>{console.log('Возникла проблема с вашей операцией выборки:'+e.message);});
Имейте в виду,что значение,возвращаемое выполненным обещанием,становится параметром,передаваемым в функцию обратного вызова следующего блока.then()
.
Примечание:.then()
/.catch()Блоки
в обещаниях в основном являются асинхронным эквивалентом попытки...catch
блока в коде синхронизации.Имейте в виду,что синхронныйtry...catch
не будет работать в асинхронном коде.
В приведенном выше разделе было много чего рассказать,поэтому давайте быстро вернемся к нему,чтобы дать вам краткое руководство,которое вы можете добавить в закладки и использовать,чтобы освежить свою память в будущем.Вам также следует еще раз пройтись по вышеуказанному разделу еще раз,чтобы убедиться,что эти концепции придерживаются.
- Когда обещание создается,оно не находится ни в успешном,ни в неудачном состоянии.Говорят,что это,на рассмотрении-.
- Когда обещание возвращается,считается,чторазрешено.
- Успешно выполненное обещание считается выполненным.Он возвращает значение,к которому можно получить доступ,привязав блок
.then()
к концу цепочки обещаний.Функция обратного вызова внутри блока.then()
будет содержать возвращаемое значение обещания. - Неудачно выполненное обещание считаетсяотклоненным.Он возвращает причину,сообщение об ошибке,объясняющее,почему обещание было отклонено.Доступ к этой причине можно получить,связав блок
.catch()
с концом цепочки обещаний.
- Успешно выполненное обещание считается выполненным.Он возвращает значение,к которому можно получить доступ,привязав блок
В приведенном выше примере показаны некоторые реальные основы использования обещаний.Теперь давайте посмотрим на некоторые более продвинутые функции.Для начала,цепочка процессов,которые должны происходить один за другим,-это нормально,но что,если вы хотите запустить какой-то код только после того,как целая куча обещаний выполнити все?
Вы можете сделать это с помощью оригинального названияPromise.all()
статический метод.Это принимает массив обещаний в качестве входного параметра и возвращает новый объектPromise
,который будет выполнен,только если и когдавсе обещанияв массиве будут выполнены.Выглядит это примерно так:
Promise.all([a,b,c]).Then(values =>{...});
Если все они выполняются,функции обратного вызова связанного блока.then()
будет передан массив,содержащий все эти результаты в качестве параметра.Если какое-либо из обещаний перешло кPromise.all()
reject,будет отклонен весь блок.
Это может быть очень полезно.Представьте,что мы получаем информацию,чтобы динамически заполнять элементы пользовательского интерфейса на нашей странице контентом.Во многих случаях имеет смысл получать все данные и только потом показывать полное содержимое,а не отображать частичную информацию.
Давайте построим еще один пример,чтобы показать это в действии.
Загрузите новую копию нашего шаблона страницы и снова поместите элемент
непосредственно перед закрывающим тегом
Загрузите наши исходные файлы (coffee.jpg, tea.jpg и description.txt) или замените их своими собственными.
В нашем скрипте мы сначала определим функцию, которая возвращает обещания, которые мы хотим отправить на
Promise.all ()
. Это было бы легко, если бы мы просто хотели запустить блокPromise.all ()
в ответ на завершение трех операцийfetch ()
. Мы могли бы просто сделать что-то вроде:пусть a = выборка (url1); пусть b = выборка (url2); пусть c = выборка (url3); Обещать.all ([a, b, c]). then (values => { ... });
Когда обещание выполнено, значения
Response
, по одному для каждой из завершенных операцийfetch ()
.Однако мы не хотим этого делать. Нашему коду безразлично, когда будут выполнены операции
fetch ()
. Вместо этого нам нужны загруженные данные. Это означает, что мы хотим запустить блокPromise.all ()
, когда мы получим обратно пригодные для использования большие двоичные объекты, представляющие изображения, и используемую текстовую строку.Мы можем написать функцию, которая сделает это; добавьте в элементfunction fetchAndDecode(url,type){return fetch(url).then(response=>{if(!response.ok){выдать новую ошибку(`Ошибка HTTP! status: $ {response.status}`);}еще{if(type==='blob'){вернуть response.blob();}else if(type==='text'){вернуть response.text();}}}).catch(e=>{console.log(`Возникла проблема с вашей операцией выборки ресурса" $ {url} ":`+e.сообщение);});}
Это выглядит немного сложно,поэтому давайте рассмотрим его шаг за шагом:
- Прежде всего,мы определяем функцию,передавая ей URL-адрес и строку,представляющую тип ресурса,который она извлекает.
- Внутри тела функции у нас есть структура,аналогичная той,которую мы видели в первом примере-мы вызываем функцию
fetch()
для выборки ресурса по указанному URL-адресу,а затем связываем его с другим обещанием,которое возвращает декодированный(или"читать")тело ответа.В предыдущем примере это всегда был методblob()
. - Однако здесь разные вещи:
- Прежде всего,второе обещание,которое мы возвращаем,различается в зависимости от значения типа
.Внутри функции обратного вызова
.then()
мы включаем простой операторif...else if
для возврата другого обещания в зависимости от того,какой тип файла нам нужно декодировать(в этом случае у нас есть выборblob
илиtext
,но это было бы легко расширить для работы с другими типами). - Во-вторых,мы добавили ключевое слово
return
перед вызовомfetch()
.В результате выполняется вся цепочка,а затем конечный результат(т.е.обещание,возвращаемое функциейblob()
илиtext()
)в качестве возвращаемого значения функции,которую мы только что определили.Фактически,операторыreturn
передают результаты обратно вверх по цепочке наверх.
- Прежде всего,второе обещание,которое мы возвращаем,различается в зависимости от значения типа
В конце блока мы цепляем
.catch()
для обработки любых случаев ошибок,которые могут возникнуть с любым из обещаний,переданных в массив в.all()
.Если какое-либо из обещаний отклоняется,блок.catch()
сообщит вам,в каком из них возникла проблема.Блок.all()
(см.Ниже)по-прежнему будет выполняться,но он не будет отображать ресурсы,у которых возникли проблемы.Помните,что после обработки обещания с помощью блока.catch()
результирующее обещание считается выполненным,но со значениемundefined
;поэтому в данном случае.Блок all()
всегда будет выполняться.Если вы хотите,чтобы.all()
отклонял,вам придется вместо этого связать блок.catch()
с концом.all()
.
Код внутри тела функции является асинхронным и основан на обещаниях,поэтому фактически вся функция действует как обещание-удобно.
Затем мы вызываем нашу функцию три раза,чтобы начать процесс выборки и декодирования изображений и текста и сохранить каждое из возвращенных обещаний в переменной.Добавьте следующее под своим предыдущим кодом:
пусть coffee=fetchAndDecode('coffee.jpg','blob');пусть tea=fetchAndDecode('tea.jpg','blob');let description=fetchAndDecode('description.txt','текст');
Затем мы определим блок
Promise.all()
для запуска некоторого кода только тогда,когда все три обещания,сохраненные выше,успешно выполнены.Для начала добавьте блок с пустой функцией обратного вызова внутри вызова.then()
,например:Обещание.все([кофе,чай,описание]).then(values =>{});
Вы можете видеть,что он принимает в качестве параметра массив,содержащий обещания.Функция обратного вызова
.then()
будет запущена только после разрешения всех трех обещаний;когда это произойдет,ему будет передан массив,содержащий результаты отдельных обещаний(то есть декодированные тела ответа),вроде[coffee-results,tea-results,description-results].Наконец,добавьте в обратный вызов следующее.Здесь мы используем довольно простой код синхронизации,чтобы сохранять результаты в отдельных переменных(создавая URL-адреса объектов из больших двоичных объектов),а затем отображать изображения и текст на странице.
console.log(значения);пусть objectURL1=URL.createObjectURL(значения[0]);пусть objectURL2=URL.createObjectURL(значения[1]);let descText=values [2];пусть image1=document.createElement('img');пусть image2=document.createElement('img');image1.src=objectURL1;image2.src=objectURL2;document.body.appendChild(изображение1);документ.body.appendChild(изображение2);пусть para=document.createElement('p');para.textContent=descText;document.body.appendChild(параграф);
Сохраните и обновите,и вы увидите,что все компоненты пользовательского интерфейса загружены,хотя и не особенно привлекательно!
Код,который мы здесь предоставили для отображения элементов,довольно примитивен,но пока работает как поясняющий.
Примечание:Если вы застряли,вы можете сравнить свою версию кода с нашей,чтобы увидеть,как она должна выглядеть-увидеть ее вживую и увидеть исходный код.
Примечание:Если вы улучшали этот код,вы могли бы захотеть перебрать список элементов для отображения,выборки и декодирования каждого из них,а затем перебрать результаты внутриPromise.all()
,запустив другую функцию.для отображения каждого из них в зависимости от типа кода.Это заставит его работать для любого количества элементов,а не только для трех.
Кроме того,вы можете определить,какой тип файла выбирается,не требуя явного свойстваtype
.Вы можете,например,проверить HTTP-заголовокContent-Type
ответа в каждом случае,используяresponse.headers.get("content-type")
,а затем отреагировать соответствующим образом.
Бывают случаи,когда вы захотите запустить последний блок кода после завершения обещания,независимо от того,выполнено оно или отклонено.Раньше вам приходилось включать один и тот же код в обратные вызовы.then()
и.catch()
,например:
myPromise.then(response=>{doSomething(ответ);runFinalCode();}).catch(e=>{returnError(e);runFinalCode();});
В более поздних современных браузерах доступен метод.finally()
,который можно привязать к концу вашей обычной цепочки обещаний,что позволяет сократить количество повторений кода и делать вещи более элегантно.Приведенный выше код теперь можно записать следующим образом:
myPromise.then(response=>{doSomething(ответ);}).catch(e=>{returnError(e);}).наконец(()=>{runFinalCode();});
В качестве реального примера взгляните на нашу демонстрацию обещания-finally.html(см.Также исходный код).Это работает так же,как и демонстрацияPromise.all()
,которую мы рассматривали в предыдущем разделе,за исключением того,что в функцииfetchAndDecode()
мы связываем вызовfinally()
с концом цепочки:
function fetchAndDecode(url,type){return fetch(url).then(response=>{if(!response.ok){выбросить новую ошибку(`Ошибка HTTP! status: $ {response.status} `);}еще{if(type==='blob'){вернуть response.blob();}else if(type==='text'){вернуть response.text();}}}).catch(e=>{console.log(`Возникла проблема с вашей операцией выборки ресурса" $ {url} ":`+e.message);}).finally(()=>{console.log(`попытка получения для" $ {url} "завершена. ');
});
}
Это записывает простое сообщение на консоль, чтобы сообщить нам, когда каждая попытка выборки завершена.
Примечание: then ()
/ catch ()
/ finally ()
является асинхронным эквивалентом try
/ catch
/ наконец
в коде синхронизации.
Хорошая новость в том, что в некотором смысле вы уже построили свои собственные обещания. Когда вы связали несколько обещаний вместе с блоками .then ()
или иным образом объединили их для создания настраиваемой функциональности, вы уже создаете свои собственные пользовательские функции на основе асинхронных обещаний. Возьмем, к примеру, нашу функцию fetchAndDecode ()
из предыдущих примеров.
Объединение различных API, основанных на обещаниях, вместе для создания настраиваемой функциональности, на сегодняшний день является наиболее распространенным способом выполнения настраиваемых действий с помощью обещаний и демонстрирует гибкость и мощь базирования большинства современных API на одном и том же принципе.Однако есть и другой способ.
Использование конструктора Promise ()
Можно создавать свои собственные обещания с помощью конструктора Promise ()
. Основная ситуация, в которой вы захотите это сделать, - это когда у вас есть код, основанный на асинхронном API старой школы, который не основан на обещаниях, который вы хотите обещать. Это удобно, когда вам нужно использовать существующий, более старый код проекта, библиотеки или фреймворки вместе с современным кодом, основанным на обещаниях.
Давайте посмотрим на простой пример, чтобы вы начали - здесь мы оборачиваем вызов setTimeout ()
с обещанием - это запускает функцию через две секунды, которая разрешает обещание (с использованием переданного вызова resolve ()
) со строкой «Успех!».
let timeoutPromise = new Promise ((разрешить, отклонить) => {
setTimeout (() => {
решить («Успех!»);
}, 2000);
});
resolve ()
и reject ()
- это функции, которые вы вызываете для выполнения или отклонения вновь созданного обещания. В этом случае обещание выполняется со строкой «Успех!».
Итак, когда вы вызываете это обещание, вы можете связать блок .then ()
с его концом, и ему будет передана строка «Успех!».В приведенном ниже коде мы предупреждаем об этом сообщении:
тайм-аут
.then ((сообщение) => {
оповещение (сообщение);
})
или даже просто
timeoutPromise.then (предупреждение);
Попробуйте запустить это в реальном времени, чтобы увидеть результат (также см. Исходный код).
Приведенный выше пример не очень гибкий - обещание может быть выполнено только с одной строкой, и в нем не указано какое-либо условие reject ()
(по общему признанию, setTimeout ()
действительно не имеет условие отказа, поэтому для этого простого примера это не имеет значения).
Примечание: Почему разрешает ()
, а не выполняет ()
? Ответ, который мы вам сейчас дадим: , это сложно .
Отклонение пользовательского обещания
Мы можем создать обещание, которое отклоняет, используя метод reject ()
- точно так же, как resolve ()
, он принимает одно значение, но в данном случае это причина для отклонения, т.е. ошибка, которая будет передана в блок .catch ()
.
Давайте расширим предыдущий пример, включив в него несколько условий reject () и
, а также разрешим передачу различных сообщений в случае успеха.
Возьмите копию предыдущего примера и замените существующее определение timeoutPromise ()
следующим:
function timeoutPromise (message, interval) {
вернуть новое обещание ((разрешить, отклонить) => {
if (message === '' || typeof message! == 'string') {
reject ('Сообщение пусто или не является строкой');
} else if (interval <0 || typeof interval! == 'число') {
reject («Интервал отрицательный или не число»);
} еще {
setTimeout (() => {
решить (сообщение);
}, интервал);
}
});
}
Здесь мы передаем два аргумента в пользовательскую функцию - сообщение, с которым нужно что-то сделать, и временной интервал, который нужно пройти перед выполнением действия.Затем внутри функции мы возвращаем новый объект Promise
- вызов функции вернет обещание, которое мы хотим использовать.
Внутри конструктора Promise мы выполняем несколько проверок внутри if ... else
структур:
- Прежде всего, мы проверяем, подходит ли сообщение для оповещения. Если это пустая строка или вообще не строка, мы отклоняем обещание с подходящим сообщением об ошибке.
- Затем мы проверяем, является ли интервал подходящим значением интервала.Если оно отрицательное или не число, мы отклоняем обещание с подходящим сообщением об ошибке.
- Наконец, если оба параметра выглядят нормально, мы разрешаем обещание с указанным сообщением после прохождения указанного интервала с помощью
setTimeout ()
.
Поскольку функция timeoutPromise ()
возвращает Promise
, мы можем связать с ней .then ()
, .catch ()
и т. Д., Чтобы использовать его функциональные возможности. Давайте использовать его сейчас - замените предыдущее использование тайм-аута Promise
на это:
timeoutPromise ('Привет!', 1000)
.then (message => {
оповещение (сообщение);
})
.catch (e => {
console.log ('Ошибка:' + e);
});
Когда вы сохраните и запустите код как есть, через одну секунду вы получите предупреждение. Теперь попробуйте установить для сообщения пустую строку или, например, интервал для отрицательного числа, и вы сможете увидеть отклонение обещания с соответствующими сообщениями об ошибках! Вы также можете попробовать сделать что-нибудь еще с решенным сообщением, а не просто предупредить его.
Более реальный пример
Приведенный выше пример был намеренно сделан простым, чтобы упростить понимание концепций, но на самом деле он не очень асинхронный.Асинхронный характер в основном подделывается с помощью setTimeout ()
, хотя он все же показывает, что обещания полезны для создания пользовательской функции с разумным потоком операций, хорошей обработкой ошибок и т. Д.
Один из примеров, который мы хотели бы пригласить для изучения, который действительно показывает полезное асинхронное приложение конструктора Promise ()
, - это библиотека idb Джейка Арчибальда. Для этого используется API IndexedDB, который представляет собой API старого стиля на основе обратных вызовов для хранения и извлечения данных на стороне клиента, и позволяет использовать его с обещаниями.В коде вы увидите, что там используются те же методы, которые мы обсуждали выше. Следующий блок преобразует базовую модель запроса, используемую многими методами IndexedDB, для использования обещаний (см., Например, этот код).
Promises - хороший способ создания асинхронных приложений, когда мы не знаем возвращаемое значение функции или сколько времени потребуется для возврата. Они упрощают выражение и обоснование последовательностей асинхронных операций без глубоко вложенных обратных вызовов, и они поддерживают стиль обработки ошибок, аналогичный синхронной попытке ... поймать
заявление.
Promises работают в последних версиях всех современных браузеров; единственное место, где поддержка обещаний будет проблемой, - это Opera Mini, IE11 и более ранние версии.
В этой статье мы не коснулись всех перспективных функций, а только самых интересных и полезных. По мере того, как вы начнете больше узнавать об обещаниях, вы столкнетесь с дополнительными функциями и методами.
Большинство современных веб-API основаны на обещаниях, поэтому вам нужно понимать обещания, чтобы получить от них максимальную отдачу.Среди этих API-интерфейсов - WebRTC, Web Audio API, Media Capture and Streams и многие другие. С течением времени обещания будут приобретать все большее значение, поэтому научиться их использовать и понимать - важный шаг в изучении современного JavaScript.
Асинхронное программирование на JavaScript и обратные вызовы
Асинхронность в языках программирования
Компьютеры асинхронны по своей конструкции.
Асинхронный означает, что все может происходить независимо от основного потока программы.
На текущих компьютерах-потребителях каждая программа выполняется в течение определенного временного интервала, а затем останавливает свое выполнение, чтобы позволить другой программе продолжить свое выполнение.Эта штука работает в цикле так быстро, что это невозможно заметить. Мы думаем, что наши компьютеры запускают множество программ одновременно, но это иллюзия (кроме многопроцессорных машин).
Программы внутренне используют прерывания , сигнал, который посылается процессору, чтобы привлечь внимание системы.
Я не буду вдаваться в подробности этого, просто имейте в виду, что это нормально, когда программы работают асинхронно и останавливают свое выполнение до тех пор, пока они не потребуют внимания, позволяя компьютеру выполнять другие вещи в это время.Когда программа ожидает ответа от сети, она не может остановить процессор до завершения запроса.
Обычно языки программирования синхронны, и некоторые из них позволяют управлять асинхронностью языка или через библиотеки. C, Java, C #, PHP, Go, Ruby, Swift и Python по умолчанию синхронны. Некоторые из них обрабатывают асинхронность с помощью потоков, порождая новый процесс.
JavaScript
JavaScript по умолчанию синхронный и однопоточный.Это означает, что код не может создавать новые потоки и работать параллельно.
Строки кода выполняются последовательно одна за другой, например:
JScopy
const a = 1
const b = 2
const c = a * b
console.log (c)
doSomething ()
Но JavaScript родился внутри браузера, его основной задачей вначале было реагирование на действия пользователя, такие как onClick
, onMouseOver
, onChange
, onSubmit
и так далее.Как это могло быть сделано с помощью модели синхронного программирования?
Ответ был в его среде. Браузер предоставляет способ сделать это, предоставляя набор API-интерфейсов, которые могут обрабатывать такие функции.
Совсем недавно Node.js представил среду неблокирующего ввода-вывода, чтобы расширить эту концепцию до доступа к файлам, сетевых вызовов и так далее.
Обратные вызовы
Вы не можете знать, когда пользователь собирается нажать кнопку. Итак, вы определяете обработчик событий для события щелчка .Этот обработчик событий принимает функцию, которая будет вызываться при срабатывании события:
JScopy
document.getElementById ('button'). AddEventListener ('click', () => {
})
Это так называемый обратный вызов .
Обратный вызов - это простая функция, которая передается в качестве значения другой функции и будет выполняться только при возникновении события. Мы можем это сделать, потому что JavaScript имеет функции первого класса, которые могут быть назначены переменным и переданы другим функциям (называемые функциями высшего порядка )
Обычно весь ваш клиентский код помещается в слушатель событий load
. на объекте window
, который запускает функцию обратного вызова только когда страница готова:
JScopy
window.addEventListener ('load', () => {
})
Обратные вызовы используются везде, а не только в событиях DOM.
Одним из распространенных примеров является использование таймеров:
JScopy
setTimeout (() => {
}, 2000)
Запросы XHR также принимают обратный вызов, в этом примере путем присвоения функции свойству, которое будет вызываться при наступлении определенного события (в этом случае изменяется состояние запроса):
JScopy
const xhr = new XMLHttpRequest ()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
xhr.status === 200? console.log (xhr.responseText): console.error ('error')
}
}
xhr.open ('GET', 'https://yoursite.com')
xhr.send ()
Обработка ошибок в обратных вызовах
Как вы обрабатываете ошибки с помощью обратных вызовов? Одна очень распространенная стратегия - использовать то, что было принято в Node.js: первым параметром в любой функции обратного вызова является объект ошибки: обратных вызовов сначала с ошибкой
Если ошибки нет, объект имеет значение null
.Если есть ошибка, он содержит некоторое описание ошибки и другую информацию.
JScopy
fs.readFile ('/ file.json', (err, data) => {
if (err) {
console.log (err)
return
}
console. log (data)
})
Проблема с обратными вызовами
Обратные вызовы отлично подходят для простых случаев!
Однако каждый обратный вызов добавляет уровень вложенности, и когда у вас много обратных вызовов, код начинает очень быстро усложняться:
JScopy
window.addEventListener ('load', () => {
document.getElementById ('button'). addEventListener ('click', () => {
setTimeout (() => {
items.forEach (item = > {
})
}, 2000)
})
})
Это простой четырехуровневый код, но я видел гораздо больше уровней вложенности, и это неинтересно.
Как решить эту проблему?
Альтернативы обратным вызовам
Начиная с ES6, JavaScript представил несколько функций, которые помогают нам с асинхронным кодом, не использующим обратные вызовы: Promises (ES6) и Async / Await (ES2017).
Как работает JavaScript: цикл событий и рост асинхронного программирования + 5 способов улучшить кодирование с помощью async / await | автор Александр Златков
Добро пожаловать в пост №4 из серии, посвященной изучению JavaScript и его компонентов. В процессе определения и описания основных элементов мы также разделяем некоторые практические правила, которые мы используем при создании SessionStack, приложения JavaScript, которое должно быть надежным и высокопроизводительным, чтобы оставаться конкурентоспособным.
Вы пропустили первые три главы? Вы можете найти их здесь:
- Обзор движка, среды выполнения и стека вызовов
- Внутри движка Google V8 + 5 советов по написанию оптимизированного кода
- Управление памятью + как справиться с 4 распространенными утечками памяти
На этот раз мы расширим нашу первую публикацию, рассмотрев недостатки программирования в однопоточной среде и способы их преодоления с помощью цикла событий и async / await для создания потрясающих пользовательских интерфейсов JavaScript.По традиции, в конце статьи мы поделимся 5 советами о том, как писать более чистый код с помощью async / await
Почему наличие одного потока является ограничением?
В первой публикации, которую мы запустили, мы размышляли над вопросом , что происходит, когда у вас есть вызовы функций в стеке вызовов, для обработки которых требуется огромное количество времени .
Представьте себе, например, сложный алгоритм преобразования изображения, работающий в браузере.
Хотя у стека вызовов есть функции, которые нужно выполнять, браузер не может делать ничего другого - он блокируется.Это означает, что браузер не может отображать, он не может запускать какой-либо другой код, он просто зависает. И здесь возникает проблема - пользовательский интерфейс вашего приложения больше не является эффективным и приятным.
Ваше приложение зависло .
В некоторых случаях это может быть не столь критично. Но послушайте - вот еще большая проблема. Как только ваш браузер начинает обрабатывать слишком много задач в стеке вызовов, он может перестать отвечать на запросы на долгое время. В этот момент многие браузеры выдают ошибку и спрашивают, следует ли закрыть страницу:
Это уродливо и полностью разрушает ваш UX:
Строительные блоки программы JavaScript
Вы может писать ваше приложение JavaScript в одном файле.js, но ваша программа почти наверняка состоит из нескольких блоков, только один из которых будет выполнять сейчас , а остальные будут выполнять позже . Самая распространенная блочная единица - это функция.
Проблема, с которой сталкивается большинство разработчиков, плохо знакомых с JavaScript, заключается в понимании того, что позже не обязательно произойдет строго сразу после , теперь . Другими словами, задачи, которые не могут выполнить теперь , по определению будут выполняться асинхронно, что означает, что у вас не будет вышеупомянутого поведения блокировки, как вы могли подсознательно ожидать или надеяться.
Давайте посмотрим на следующий пример:
Вы, вероятно, знаете, что стандартные запросы Ajax не выполняются синхронно, что означает, что во время выполнения кода функция ajax (..) еще не имеет значения. чтобы вернуться обратно для присвоения переменной ответа.
Простым способом «подождать», пока асинхронная функция вернет свой результат, является использование функции, называемой обратным вызовом :
Просто примечание: на самом деле вы можете сделать синхронных запросов Ajax.Никогда, никогда не делай этого. Если вы сделаете синхронный запрос Ajax, пользовательский интерфейс вашего приложения JavaScript будет заблокирован - пользователь не сможет нажимать, вводить данные, перемещаться или прокручивать. Это предотвратит любое взаимодействие с пользователем. Это ужасная практика.
Вот как это выглядит, но, пожалуйста, никогда не делайте этого - не портите Интернет:
Мы использовали Ajax-запрос в качестве примера. Любой фрагмент кода может выполняться асинхронно.
Это можно сделать с помощью функции setTimeout (обратный вызов, миллисекунды)
.Функция setTimeout
устанавливает событие (тайм-аут), которое должно произойти позже. Давайте посмотрим:
Вывод в консоли будет следующим:
первый
третий
второй
Что такое цикл событий?
Мы начнем с довольно странного утверждения - несмотря на то, что разрешен асинхронный код JavaScript (например, setTimeout
, который мы только что обсуждали), до ES6 сам JavaScript фактически никогда не имел прямого понятия асинхронности.Механизм JavaScript никогда не делал ничего, кроме выполнения отдельного фрагмента вашей программы в любой момент.
Подробнее о том, как работают движки JavaScript (в частности, Google V8), читайте в одной из наших предыдущих статей по этой теме.
Итак, кто приказывает JS Engine выполнять фрагменты вашей программы? На самом деле JS Engine не работает изолированно - он работает в среде хоста , которая для большинства разработчиков является типичным веб-браузером или Node.js.На самом деле, в настоящее время JavaScript встраивается во все виды устройств, от роботов до лампочек. Каждое устройство представляет собой отдельный тип среды хостинга для JS Engine.
Общим знаменателем во всех средах является встроенный механизм, называемый циклом событий, который обрабатывает выполнение нескольких фрагментов вашей программы с течением времени, каждый раз вызывая JS Engine.
Это означает, что JS Engine - это просто среда выполнения по запросу для любого произвольного кода JS.Это окружающая среда, которая планирует события (выполнение кода JS).
Так, например, когда ваша программа JavaScript делает запрос Ajax для получения некоторых данных с сервера, вы устанавливаете код «ответа» в функции («обратный вызов»), а JS Engine сообщает среде хостинга:
«Эй, я собираюсь приостановить выполнение на данный момент, но всякий раз, когда вы закончите с этим сетевым запросом и у вас будут некоторые данные, пожалуйста, вызовите эту функцию назад .”
Затем браузер настраивается для прослушивания ответа от сети, и когда ему есть что вернуть, он планирует выполнение функции обратного вызова, вставляя ее в цикл событий .
Давайте посмотрим на диаграмму ниже:
Вы можете узнать больше о куче памяти и стеке вызовов в нашей предыдущей статье.
А что это за веб-API? По сути, это цепочки, к которым у вас нет доступа, вы можете просто звонить в них.Это части браузера, в которых задействован параллелизм. Если вы разработчик на Node.js, то это API C ++.
Так что же такое цикл событий
после всех ?Цикл событий выполняет одну простую задачу - контролировать стек вызовов и очередь обратных вызовов. Если стек вызовов пуст, цикл событий возьмет первое событие из очереди и отправит его в стек вызовов, который эффективно его запускает.
Такая итерация называется тиком в цикле событий.Каждое событие - это просто обратный вызов функции.
Давайте «выполним» этот код и посмотрим, что произойдет:
- Состояние ясно. Консоль браузера чистая, стек вызовов пуст.
2. console.log ('Hi')
добавлен в стек вызовов.
3. Выполняется console.log ('Hi')
.
4. console.log ('Hi')
удален из стека вызовов.
5. setTimeout (function cb1 () {...})
добавлен в стек вызовов.
6. setTimeout (функция cb1 () {...}) выполняется
. Браузер создает таймер как часть веб-API. Он сделает обратный отсчет за вас.
7. setTimeout (function cb1 () {...}) сам
завершен и удаляется из стека вызовов.
8. console.log («Пока»)
добавлен в стек вызовов.
9. console.log ('Пока')
выполняется.
10. console.log («Пока»)
удален из стека вызовов.
11. По истечении не менее 5000 мс таймер завершает работу и отправляет обратный вызов cb1
в очередь обратного вызова.
12. Цикл событий берет cb1
из очереди обратного вызова и помещает его в стек вызовов.
13. cb1
выполняется и добавляет console.log ('cb1')
в стек вызовов.
14. console.log ('cb1')
выполняется.
15. console.log ('cb1')
удален из стека вызовов.
16. cb1
удаляется из стека вызовов.
Краткое резюме:
Интересно отметить, что ES6 определяет, как должен работать цикл событий, а это означает, что технически он входит в сферу ответственности JS-движка, который больше не играет только роль среды хостинга. Одной из основных причин этого изменения является введение обещаний в ES6, поскольку последние требуют доступа к прямому, детализированному контролю над операциями планирования в очереди цикла событий (мы обсудим их более подробно позже).
Как работает setTimeout (…)
Важно отметить, что setTimeout (…)
не помещает ваш обратный вызов автоматически в очередь цикла событий. Устанавливает таймер. Когда таймер истекает, среда помещает ваш обратный вызов в цикл событий, так что какой-то будущий тик подхватит его и выполнит. Взгляните на этот код:
Это не означает, что myCallback
будет выполнено за 1000 мс, а скорее, что через 1000 мс myCallback
будет добавлен в очередь цикла событий.Однако в очереди могут быть другие события, которые были добавлены ранее - вашему обратному вызову придется подождать.
Существует довольно много статей и руководств по началу работы с асинхронным кодом в JavaScript, в которых предлагается выполнить setTimeout (callback, 0)
. Что ж, теперь вы знаете, что делает цикл событий и как работает setTimeout: вызов setTimeout с 0 в качестве второго аргумента просто откладывает обратный вызов до тех пор, пока не очистится стек вызовов.
Взгляните на следующий код:
Хотя время ожидания установлено на 0 мс, результат в консоли браузера будет следующим:
Привет
Пока
обратный вызов
Что такое задания в ES6?
В ES6 была представлена новая концепция под названием «Очередь заданий».Это слой над очередью цикла событий. Вы, скорее всего, столкнетесь с этим, когда будете иметь дело с асинхронным поведением промисов (мы тоже поговорим о них).
Мы просто коснемся этой концепции сейчас, чтобы позже, когда мы обсудим асинхронное поведение с обещаниями, вы поняли, как эти действия планируются и обрабатываются.
Представьте себе это так: очередь заданий - это очередь, которая прикрепляется к концу каждого тика в очереди цикла событий. Некоторые асинхронные действия, которые могут произойти во время тика цикла событий, не вызовут добавление целого нового события в очередь цикла событий, а вместо этого добавят элемент (также известный как задание) в конец очереди заданий текущего тика.
Это означает, что вы можете добавить еще одну функцию, которая будет выполняться позже, и можете быть уверены, что она будет выполнена сразу после, прежде чем что-либо еще.
Задание также может привести к добавлению большего количества заданий в конец той же очереди. Теоретически возможно, что «цикл» задания (задание, которое продолжает добавлять другие задания и т. Д.) Может вращаться бесконечно, что лишает программу необходимых ресурсов, необходимых для перехода к следующему такту цикла событий. Концептуально это было бы похоже на простое выражение продолжительного или бесконечного цикла (например, , а (истинно)
..) в вашем коде.
Задания похожи на «взлом» setTimeout (callback, 0)
, но реализованы таким образом, что они вводят гораздо более четко определенный и гарантированный порядок: позже, но как можно скорее.
Обратные вызовы
Как вы уже знаете, обратные вызовы на сегодняшний день являются наиболее распространенным способом выражения асинхронности в программах на JavaScript и управления ею. Действительно, обратный вызов - это самый фундаментальный асинхронный шаблон в языке JavaScript. Бесчисленные программы JS, даже очень сложные и сложные, были написаны не на основе какой-либо другой асинхронной основы, кроме обратного вызова.
За исключением того, что обратные вызовы не имеют недостатков. Многие разработчики пытаются найти лучшие асинхронные шаблоны. Однако невозможно эффективно использовать какую-либо абстракцию, если вы не понимаете, что на самом деле находится под капотом.
В следующей главе мы подробно рассмотрим пару этих абстракций, чтобы показать, почему более сложные асинхронные шаблоны (которые будут обсуждаться в следующих статьях) необходимы и даже рекомендуются.
Вложенные обратные вызовы
Посмотрите на следующий код:
У нас есть цепочка из трех вложенных вместе функций, каждая из которых представляет шаг в асинхронной последовательности.
Такой код часто называют «адом обратного вызова». Но «ад обратных вызовов» на самом деле почти не имеет ничего общего с вложением / отступом. Это гораздо более серьезная проблема.
Сначала мы ждем события «щелчок», затем мы ждем, пока сработает таймер, затем мы ждем возврата ответа Ajax, после чего все может повториться снова.
На первый взгляд может показаться, что этот код естественным образом отображает свою асинхронность на последовательные шаги, например:
Затем мы имеем:
Затем мы имеем:
И, наконец:
Итак, такой последовательный способ выражения асинхронности код кажется намного более естественным, не так ли? Должен же быть такой способ, правда?
Promises
Взгляните на следующий код:
Все очень просто: он суммирует значения x
и y
и выводит его на консоль.Что делать, если значение x
или y
отсутствовало и еще не было определено? Скажем, нам нужно получить значения x
и y
с сервера, прежде чем их можно будет использовать в выражении. Представим, что у нас есть функции loadX
и loadY
, которые соответственно загружают с сервера значения x
и y
. Затем представьте, что у нас есть функция sum
, которая суммирует значения x
и y
после их загрузки.
Это могло бы выглядеть так (довольно уродливо, не так ли):
Здесь есть кое-что очень важное - в этом фрагменте мы обработали x
и y
как будущих значений , и мы выразили операцию Сумма (…)
, которую (извне) не волновало, будут ли x
или y
или оба сразу доступны или нет.
Конечно, такой грубый подход, основанный на обратных вызовах, оставляет желать лучшего. Это всего лишь первый крошечный шаг к пониманию преимуществ рассуждения о будущих значениях , не беспокоясь о временном аспекте того, когда они будут доступны.
Promise Value
Давайте кратко рассмотрим, как мы можем выразить пример x + y
с помощью обещаний:
В этом фрагменте кода есть два уровня обещаний.
fetchX ()
и fetchY ()
вызываются напрямую, и возвращаемые ими значения (обещания!) Передаются в sum (...)
. Базовые значения, которые представляют эти обещания, могут быть готовы сейчас или позже , но каждое обещание нормализует свое поведение, чтобы оно оставалось неизменным.Мы рассуждаем о значениях x
и y
независимо от времени. Это фьючерсные значения , период.
Второй уровень - это обещание, что sum (...)
создает
(через Promise.all ([...])
) и возвращает, чего мы ждем, вызывая , затем (...)
. Когда операция суммы (...)
завершается, наша сумма будущее значение готова, и мы можем ее распечатать. Мы скрываем логику ожидания будущих значений x
и y
внутри сумм (...)
.
Примечание : Внутри суммы (…)
вызов Promise.all ([…])
создает обещание (которое ожидает разрешения на promiseX
и promiseY
). Связанный вызов .then (...)
создает другое обещание, которое строка return
values [0] + values [1]
немедленно разрешает (с результатом сложения). Таким образом, вызов then (...)
, который мы связываем с концом sum (...) вызов
- в конце фрагмента - фактически работает с этим вторым возвращенным обещанием, а не первым. Создано Promise.все ([...])
. Кроме того, хотя мы не связываем конец этого второго , затем (...)
, он также создал еще одно обещание, если бы мы решили наблюдать / использовать его. Этот материал цепочки обещаний будет объяснен более подробно позже в этой главе.
С помощью Promises вызов then (...)
может фактически принимать две функции: первая для выполнения (как показано ранее), а вторая для отклонения:
Если что-то пошло не так при получении x
или y
, или что-то как-то не получилось при сложении, обещание, что сумма (...) Возвраты
будут отклонены, и второй обработчик ошибок обратного вызова будет передан на , тогда (...)
получит значение отклонения из обещания.
Поскольку обещания инкапсулируют зависящее от времени состояние - ожидание выполнения или отклонения базового значения - извне, само обещание не зависит от времени, и, таким образом, обещания могут быть составлены (объединены) предсказуемым образом независимо от времени. или результат внизу.
Более того, как только обещание разрешено, оно остается таким навсегда - в этот момент оно становится неизменным значением - и затем может быть наблюдаться столько раз, сколько необходимо.
Очень полезно, что вы можете связать обещания:
Задержка вызова (2000)
создает обещание, которое будет выполнено в течение 2000 мс, а затем мы возвращаем его из первого , затем (...) обратный вызов выполнения
, который вызывает второй , затем (...) обещание
ждать выполнения этого обещания 2000 мс.
Примечание : Поскольку Promise является внешне неизменяемым после разрешения, теперь можно безопасно передать это значение любой стороне, зная, что оно не может быть изменено случайно или злонамеренно.Это особенно верно в отношении нескольких сторон, соблюдающих выполнение обещания. Одна сторона не может повлиять на способность другой стороны выполнять обещания. Неизменяемость может звучать как академическая тема, но на самом деле это один из самых фундаментальных и важных аспектов дизайна Promise, и его нельзя упускать из виду.
Обещать или не обещать?
Важная деталь об обещаниях - это знать наверняка, является ли какое-то значение фактическим обещанием или нет.Другими словами, будет ли это значение вести себя как обещание?
Мы знаем, что Promise создаются с помощью нового синтаксиса Promise (…)
, и вы можете подумать, что p instanceof Promise
будет достаточной проверкой. Не совсем так.
В основном потому, что вы можете получить значение Promise из другого окна браузера (например, iframe), которое будет иметь собственное обещание, отличное от того, которое находится в текущем окне или фрейме, и эта проверка не сможет идентифицировать экземпляр Promise.
Более того, библиотека или фреймворк могут предлагать свои собственные Promise и не использовать для этого встроенную реализацию ES6 Promise. Фактически, вы вполне можете использовать Promises с библиотеками в старых браузерах, в которых вообще нет Promise.
Исключения проглатывания
Если в любой момент при создании обещания или при наблюдении за его разрешением возникает ошибка исключения JavaScript, такая как TypeError
или ReferenceError
, это исключение будет перехвачено, и оно заставит данное Обещание быть отклоненным.
Например:
Но что произойдет, если обещание выполнено, но во время наблюдения возникла ошибка исключения JS (в случае , то (…) зарегистрированного обратного вызова
)? Даже если это не будет потеряно, вы можете найти то, как с ними обращаются, немного удивительным. Пока вы не копнете немного глубже:
Похоже, исключение из foo.bar ()
действительно было проглочено. Однако это было не так. Однако было что-то более глубокое, что пошло не так, к чему мы не прислушались.Сам вызов p.then (…)
возвращает другое обещание, и именно это обещание будет отклонено с исключением TypeError
.
Обработка неперехваченных исключений
Существуют и другие подходы, которые, по мнению многих, лучше .
Часто предлагается, чтобы к Promises было добавлено done (…)
, что по существу помечает цепочку Promise как «выполненную». done (…)
не создает и не возвращает Promise, поэтому обратные вызовы передаются в done (..)
, очевидно, не подключены, чтобы сообщать о проблемах цепному обещанию, которого не существует.
Это обрабатывается так, как вы обычно ожидаете в условиях неперехваченной ошибки: любое исключение внутри обработчика отклонения done (..)
будет выдано как глобальная неперехваченная ошибка (в основном в консоли разработчика):
Что происходит в ES8? Async / await
JavaScript ES8 представил async / await
, что упрощает работу с обещаниями.Мы кратко рассмотрим возможности async / await
и способы их использования для написания асинхронного кода.
Как использовать async / await?
Вы определяете асинхронную функцию, используя объявление функции async
. Такие функции возвращают объект AsyncFunction. Объект AsyncFunction
представляет асинхронную функцию, которая выполняет код, содержащийся в этой функции.
Когда вызывается асинхронная функция, она возвращает Promise
.Когда асинхронная функция возвращает значение, которое не является обещанием
, автоматически создается обещание
, которое будет разрешено с помощью значения, возвращенного функцией. Когда функция async
выдает исключение, обещание
будет отклонено с выданным значением.
Функция async
может содержать выражение await
, которое приостанавливает выполнение функции и ожидает разрешения переданного обещания, а затем возобновляет выполнение функции async и возвращает разрешенное значение.
Вы можете думать о Promise
в JavaScript как об эквиваленте Java Future
или C #
Task.
Цель
async / await
- упростить использование обещаний.
Давайте посмотрим на следующий пример:
Точно так же функции, генерирующие исключения, эквивалентны функциям, возвращающим обещания, которые были отклонены:
Ключевое слово await
может использоваться только в функциях async
и позволяет синхронно ожидать выполнения обещания.Если мы используем обещания вне функции async и
, нам все равно придется использовать обратные вызовы , затем
:
. Вы также можете определять асинхронные функции с помощью «выражения асинхронной функции». Выражение асинхронной функции очень похоже на оператор асинхронной функции и имеет почти такой же синтаксис. Основное различие между выражением асинхронной функции и оператором асинхронной функции - это имя функции, которое можно не указывать в выражениях асинхронной функции для создания анонимных функций.Выражение асинхронной функции может использоваться как IIFE (выражение немедленного вызова функции), которое запускается сразу после его определения.
Это выглядит так:
Что еще более важно, async / await поддерживается во всех основных браузерах:
Если эта совместимость не то, что вам нужно, есть также несколько транспиляторов JS, таких как Babel и TypeScript.В конце концов, важно не выбирать слепо «последний» подход к написанию асинхронного кода. Важно понимать внутреннее устройство асинхронного JavaScript, понимать, почему он так важен, и глубоко понимать внутреннее устройство выбранного вами метода.У каждого подхода есть свои плюсы и минусы, как и у всего остального в программировании.
- Чистый код: Использование async / await позволяет писать намного меньше кода. Каждый раз, когда вы используете async / await, вы пропускаете несколько ненужных шагов: напишите. Затем создайте анонимную функцию для обработки ответа, назовите ответ из этого обратного вызова, например.
Versus:
2. Обработка ошибок: Async / await позволяет обрабатывать как ошибки синхронизации, так и ошибки async с помощью одной и той же конструкции кода - хорошо известных операторов try / catch.Давайте посмотрим, как это выглядит с Promises:
Versus:
3. Conditionals: Написание условного кода с помощью async / await
намного проще:
Versus:
4. Stack Frames: В отличие от с async / await
, стек ошибок, возвращаемый из цепочки обещаний, не дает представления о том, где произошла ошибка. Посмотрите на следующее:
Versus:
5. Отладка: Если вы использовали обещания, вы знаете, что их отладка - это кошмар.Например, если вы установите точку останова внутри блока .then и используете ярлыки отладки, такие как «stop-over», отладчик не перейдет к следующему .then, потому что он только «проходит» через синхронный код.
С помощью async / await
вы можете выполнять вызовы await точно так же, как если бы они были обычными синхронными функциями.
Почему подключение
асинхронного кода JavaScript важно для библиотек?Позвольте мне привести пример с нашим собственным продуктом SessionStack.Библиотека SessionStack записывает все, что есть в вашем веб-приложении / веб-сайте: все изменения DOM, взаимодействия с пользователем, исключения JavaScript, трассировки стека, неудачные сетевые запросы и сообщения отладки.
И все это должно происходить в вашей производственной среде, не влияя на UX. Нам нужно сильно оптимизировать наш код и сделать его максимально асинхронным, чтобы мы могли увеличить количество событий, обрабатываемых циклом событий.
И не только библиотека! Когда вы воспроизводите сеанс пользователя в SessionStack, мы должны отобразить все, что произошло в браузере вашего пользователя в момент возникновения проблемы, и мы должны восстановить все состояние, позволяя вам перемещаться вперед и назад по временной шкале сеанса.Чтобы сделать это возможным, мы активно используем асинхронные возможности, которые предоставляет JavaScript.
Существует бесплатная пробная версия, если вы хотите попробовать SessionStack.
Ресурсы:
Асинхронный JavaScript: обещания, обратные вызовы, асинхронное ожидание
Моя основная цель - помочь вам освоить Асинхронный JavaScript . Этот курс был специально разработан для тех, кто хочет улучшить свои навыки работы с обратными вызовами, обещаниями, асинхронным ожиданием и циклом событий.Этот курс был разработан так, чтобы было легко понять , и поэтому в нем много наглядных материалов, особенно когда мы говорим о важных концепциях. Вы также увидите множество примеров кодирования по пути.
Мы будем говорить о трех основных компонентах асинхронного JavaScript: функциях обратного вызова, обещаниях и асинхронном ожидании.
Обратные вызовы в JavaScript используются повсюду. Создание обработчиков событий, выполнение HTTP-запросов, взаимодействие с DOM, установка тайм-аутов, чтение или запись данных в файловую систему, работа с базами данных и т. Д.Я почти уверен, что вы уже используете обратные вызовы в своем коде, но я не уверен, знаете ли вы, как они на самом деле работают ... Знание того, как все работает на самом деле, поможет вам быстрее писать код и избегать странных ошибок.
Обещания были созданы как лучшая альтернатива обратным вызовам. У них нет недостатков обратных вызовов. Обещания JavaScript невероятно полезны, когда у вас есть несколько зависимых друг от друга асинхронных операций. Однако обещания - это новая концепция, которую вам нужно изучить, и этот курс поможет вам в этом.
Async Await - это синтаксический сахар вокруг Promises, представленный в EcmaScript 8. До этого написание асинхронного кода на JavaScript сильно отличалось от написания обычного синхронного кода. Async await позволяет вам структурировать весь ваш код аналогичным образом, независимо от того, синхронный он или асинхронный.
В популярных библиотеках и фреймворках JavaScript происходит множество асинхронных операций: React, Angular, Vue.js, jQuery и т. Д. В NodeJS практически невозможно написать что-либо без использования асинхронных операций.Посмотрев этот курс, вы сможете эффективно читать и писать асинхронный код JavaScript, и вы обязательно поймете, как он работает за кулисами!
Как работать с асинхронным JavaScript: путь 2021 года
Введение
Самая типичная задача асинхронного программирования - делать десятки вызовов AJAX на странице. От того, как вы решите работать с асинхронными вызовами, зависит будущее вашего приложения, будет ли оно хорошо работать на рынке или потерпит неудачу.
Краткая история асинхронного JavaScript
Наиболее очевидное решение пришло в виде вложенных функций в качестве обратных вызовов. Это решение привело к проблеме, широко известной разработчикам как ад обратного вызова, что затруднило управление и отладку кода. Даже сегодня многие приложения чувствуют себя плохо.
Затем мы получили обещания. Используя этот шаблон, код стало намного проще обрабатывать, но многие случаи одного и того же кода повторялись для правильного управления потоком приложения.Это прямо противоречило принципу «Не повторяйся» (DRY).
Последнее дополнение появилось в форме операторов async / await, которые, наконец, сделали асинхронный код в JavaScript легким для чтения и записи, как любой другой фрагмент кода.
Давайте взглянем на примеры каждого из этих решений и поразмышляем об эволюции асинхронного программирования в JavaScript.
Выполните эту простую задачу, чтобы помочь нам понять концепции:
- Проверьте имя пользователя и пароль пользователя.
- Получить задачи для пользователя.
- Регистрировать время доступа пользователя.
Подход 1: Обратный вызов / Ад обратного вызова / Пирамида Судьбы
Использование вложенных обратных вызовов было древним подходом, который использовался для синхронизации асинхронного кода. Со временем этот подход потерял свою популярность, поскольку он усложнил код, что повлияло на масштабируемость приложения, и все благодаря проблеме, известной как ад обратного вызова.
Каждая функция получает аргумент, который представляет собой другую функцию, вызываемую с параметром, который является ответом на предыдущее действие.Поскольку эти функции имеют тенденцию становиться все более и более сложными, вложение начинает расширяться, и для разработчика становится кошмаром оставаться в курсе внутренних условных операторов и вложенности.
Слишком много людей испытают головную боль, просто прочитав приведенный выше код. Наличие приложения с множеством похожих блоков кода вызовет еще больше проблем для человека, обслуживающего код, несмотря на то, что они написали его сами.
Как только вы поймете, что db.getTasks - это еще одна функция, содержащая вложенные обратные вызовы, этот пример станет еще более сложным.
Помимо сложного в сопровождении кода, принцип «Не повторяйся» (DRY) в данном случае не имеет абсолютно никакого значения. Например, обработка ошибок повторяется в каждой функции, и поэтому основной обратный вызов вызывается из каждой вложенной функции. Более сложные асинхронные операции JavaScript, такие как циклическое выполнение асинхронных вызовов, представляют собой еще более серьезную проблему.
Вот где JavaScript обещает (вроде) спасти положение.
Подход 2: Обещания
Для того, чтобы избежать ада обратных вызовов, обещания казались следующим логическим шагом. Несмотря на то, что этот метод не смог устранить использование обратных вызовов, он упростил цепочку функций и упростил код, что значительно облегчило чтение.
Что такое обещание?
Обещания играют роль заполнителей для значений, которые неизвестны, когда они были созданы / сформированы. Этот подход позволяет пользователю прикреплять обработчики либо к значению успеха, либо к причине сбоя асинхронного действия.Это позволяет асинхронным методам возвращать значения, как если бы они были синхронными. Этот асинхронный метод вместо прямого возврата вычисленного значения для длительной операции предоставляет значение-заполнитель и обещание доставить фактическое значение в какой-то момент вскоре после завершения трудоемкого процесса.
У обещания есть обработчики, которые ставятся в очередь до его метода then, вызываемого, когда обещание выполнено или отклонено.
При наличии обещаний наш код выглядел бы намного чище и проще для понимания.
Чтобы достичь такой простоты, функции, используемые в примере, должны быть промисифицированы. Давайте посмотрим, как будет обновлен метод getTasks, чтобы он возвращал Promise:
.
Мы изменили метод getTasks, чтобы он возвращал Promise с двумя обратными вызовами, и само обещание выполняет действия из этого метода. Обратные вызовы reject и resolve будут назначены методам обещания.then и обещание.catch соответственно.
Вы обнаружите, что метод getTasks все еще внутренне подвержен феномену пирамиды гибели.Это связано с тем, как создаются методы базы данных, поскольку они не предоставляют нам обещаний. Если бы наши методы доступа к базе данных также возвращали Promise, процесс getTasks выглядел бы аналогично приведенному ниже коду:
Подход 3: Async / Await
С введением обещаний проблема пирамиды гибели была значительно смягчена. Даже с использованием обещаний проблема обратных вызовов все еще существовала. Нам по-прежнему приходилось полагаться на обратные вызовы, переданные в.then и методы .catch обещания.
ECMAScript 2017 принес async / await, который был синтаксическим сахаром поверх Promises. Это было одно из самых крутых улучшений в JavaScript.
Async / Await был одним из самых крутых улучшений в JavaScript, который был введен в качестве синтаксического сахара поверх обещаний в ECMAScript 2017. Он позволил разработчикам писать асинхронный код на основе обещаний, который ведет себя так же, как любой синхронный код, но не блокирует основной поток, как показано в этом примере кода:
Метод verifyUser должен быть определен с использованием асинхронной функции только внутри этих функций; можно ждать обещаний.Это небольшое изменение позволяет вам ожидать обещаний, не внося никаких других изменений в другие существующие методы.
Асинхронные функции - это следующий логический шаг в эволюции асинхронного программирования в JavaScript. Они сделают ваш код намного чище и проще в управлении. Объявление функции async гарантирует, что она всегда будет возвращать Promise, поэтому вам больше не нужно о ней беспокоиться. Если у вас возникнут проблемы, вы всегда можете проконсультироваться или нанять разработчика Node.js, который поможет вам.
Преимущества использования Async-Await:
- Обработка ошибок намного проще, и она полагается на try / catch, как и другой синхронный код.
- Полученный код намного чище.
- Отладка намного проще. Вы сможете выполнять вызовы с ожиданием, как если бы они были синхронными.
Заключение
Ошибки Javascript привели к появлению расширенной версии, которая может обрабатывать более быструю обработку - асинхронного Javascript.По сути, Javascript позволяет выполнять только одно событие одновременно, поскольку он работает в одном потоке. Чтобы компенсировать это и ускорить процесс, был введен AJAX, который развивается для обновления процесса веб-разработки.
В этом блоге мы попытались предугадать, как разработчики веб-разработки, такие как вы, смогут справиться с AJAX в 2021 году. Хотя это может быть комбинация основных и нескольких продвинутых принципов, мотив - помочь сообществу. В случае возникновения дополнительных вопросов, не стесняйтесь заглядывать в раздел комментариев ниже!
Краткая история асинхронного JS
Зачем нам вообще нужны асинхронные вызовы?
Асинхронные вызовы могут вызвать путаницу, но они необходимы.Почему, спросите вы. Это потому, что JavaScript однопоточный, и из-за этого, когда цикл обработки событий обрабатывает JS-код, он не может обрабатывать обновления DOM - это означает отсутствие перекраски, никаких взаимодействий и так далее. Вы можете поиграть с небольшим примером здесь: http://event-loop-tests.glitch.me/ while-true-test.html Нажмите кнопку и попробуйте выделить текст (в Firefox и Safari даже GIF зависает!)
Представьте себе ситуацию, когда ваш скрипт подключается к API, чтобы получить из него некоторые данные, и вся страница зависает и становится неинтерактивным.Кошмар с точки зрения пользователя! Особенно в наши дни, когда JS используется не только как язык для простых взаимодействий, но отвечает за создание целых приложений и интеграцию с несколькими API.
Вот почему понимание асинхронного JS так важно, особенно для разработчиков, которые хотят создавать бэкэнды на основе JS, где эффективное использование цикла событий имеет решающее значение для производительности.
Обратный звонок
Вначале были обратные звонки… То есть хаос! Извините, я часто путаю эти два понятия.
Что такое обратный звонок?
Обратный вызов - это очень простая концепция, это функция, которая передается в качестве аргумента другой функции, которая должна быть вызвана
Это. Я уверен, что вы используете его ежедневно, даже если неосознанно. Это происходит всякий раз, когда вы прикрепляете событие
слушатель с EventTarget.addEventListener
. addEventListener
- это метод EventTarget
, который принимает не менее двух
аргументы - тип
и прослушиватель
, который является функцией, которую мы хотим выполнить, когда произойдет указанное событие.
К чему шутки?
Я доказал, что обратные вызовы полезны и широко используются, поэтому можно спросить, почему о них так много шуток? Там есть нет проблем с самими обратными вызовами, но с тем, как они используются.
Контракт асинхронной функции, построенной с помощью обратных вызовов, выглядит следующим образом: asyncFunction (params, callback)
.
Как разработчики, мы обычно хотим выполнять одно действие за другим, часто асинхронно, поэтому с кодом, который выглядит так:
doAsyncCall (params, function (error, results) {
if (error) {
handleErrors (ошибка);
} еще {
doSecondAsyncCall (результаты, функция (ошибка, resultsFromSecondCall) {
if (error) {
handleErrors (ошибка);
} еще {
консоль.log ('результаты второго асинхронного вызова', resultsFromSecondCall);
}
});
}
});
Представьте, что вы добавляете еще несколько веток и выполняете асинхронные вызовы в обработчиках ошибок - этот код быстро становятся недоступными для обслуживания и крайне нечитаемыми.
Это называется ... Ад, хаос,
, я имею ввиду Обратный вызов, ад
!
Мы, конечно, можем выделить каждую из этих встроенных функций в отдельное функциональное выражение, но эти функции часто настолько специфичны для этого одного варианта использования, что в конечном итоге мы получаем набор функций, которые трудно использовать повторно.
Мы также можем игнорировать все эти проверки ошибок, но ... думать только о счастливом пути считается плохой практикой.
Обратный вызов Ад
Если вам интересно узнать больше об аду обратных вызовов и о том, как его избежать, вот полезная ссылка. Обратный звонок Hell
Вот как может выглядеть ад обратного вызова:
a (resultsFromA => {
b (resultsFromA, resultsFromB => {
c (resultsFromB, resultsFromC => {
d (resultsFromC, resultsFromD => {
e (resultsFromD, resultsFromE => {
f (resultsFromE, resultsFromF => {
консоль.журнал (resultsFromF);
});
});
});
});
});
});
Но потом… кто-то сделал
Обещания
Что такое обещание?
Обещание - это особый тип объекта, который помогает нам, разработчикам, писать более чистый асинхронный код. Собственно, обещания база текущего асинхронного JS.
«Как насчет async / await
?» «Шшш, мы туда доберемся, и тогда ты увидишь».
Обещание похоже на обещание в реальной жизни - в конце концов что-то произойдет.
Представьте, что вы в пиццерии заказываете пиццу (точно так же, как JS, когда он заказывает какие-то ресурсы из Интернета). Кассир (браузер) дает нам обещание, что как только пицца будет готова, он принесет ее вам, и ЗАТЕМ вы сможете ее съесть (обработать). Получив это обещание, повар не заблокирует вас, и вам не придется ждать его у прилавка, но вы можете сесть за своим столом и займитесь чем-нибудь более важным, например, прокрутите страницу Facebook.
Как это работает?
Как я уже сказал, промис - это особый тип объекта, который может иметь одно из трех состояний:
- pending - обещание обрабатывается.(например, запрос на выборку еще не решен).
- выполнено - операция успешно завершена (например, запрос на выборку получил данные)
- отклонено - что-то пошло не так, операция не удалась (например, запрос на выборку обнаружил ошибку)
Как построить обещание?
Promise API поставляется с функцией-конструктором, которая принимает функцию в качестве аргумента. Этот обратный вызов также занимает два функции как аргументы.
const myPromise = new Promise ((разрешить, отклонить) => {…})
-
resolve
- вызвать эту функцию, когда обещание должно быть разрешено (выполнено).Переданный ему аргумент будет данными вернулся из обещания. -
отклонить
- вызвать эту функцию, когда обещание должно быть отклонено. Аргумент, переданный этой функции, будет ошибкой с этим можно справиться.
Простое обещание может выглядеть так:
const myPromise = new Promise (resolve => setTimeout (() => resolve (10), 5000));
В следующем примере переменной myPromise
назначается новое обещание. Вначале это обещание будет отложено.
и примерно через 5 секунд он изменит свой статус на полный
Примечание.Не обращайте внимания на это 23
. Консоль возвращает значение из последней строки. В этом случае - 23, что является идентификатором этого
setTimeout
метод.
Если увидеть этот пример в «реальной жизни», остается только один вопрос:
Как вернуть значение из обещания?
Я еще ничего не сказал об объекте обещания, но оставил небольшую подсказку в примере с пиццерией. Обещания
потом
'в состоянии. Это означает, что мы можем вызвать для них специальный метод из их прототипа - , затем
.
Затем
принимает две функции (это важно и часто приводит к побочным эффектам) в качестве аргументов - onFulfilled
(обязательно)
и на отклоненном
(необязательно).
-
onFulfilled
- вызывается, когда обещание достигает состояниявыполнено и
. -
onRejected
- вызывается, когда одно из предыдущих обещаний достигает состоянияотклонено
. Имейте в виду, эта функция не будет выполняться, если ошибка возникает в обработчикеonFulfilled
того же оператора, затем
.
Тогда
всегда возвращает обещание! Все, что возвращается от одного из обработчиков ( при выполнении
или при отклонении
), является
завернутый в обещание. Это означает:
- , если обработчик возвращает число «тогда», автоматически преобразует это число в выполненное обещание со значением этот номер.
- Если обработчик возвращает обещание, то это также возвращается значение из
, а затем из
. - Если обработчик возвращает
undefined
,, тогда
возвращает выполненное обещание со значением, установленным наundefined
.Все функции в JS без явногоreturn
return undefined. - Если обработчик выдает ошибку, то
, а затем
возвращает отклоненное обещание со значением, равным этой ошибке.
Поскольку , затем
возвращает другое обещание, мы можем добавить еще один оператор , затем
к этому обещанию и так далее, это называется
Цепочка обещаний
Если это кажется сложным, не волнуйтесь, это так. Однако я постараюсь объяснить это более подробно на примерах.
const myPromise = Promise.resolve (10);
const newPromise = myPromise
.then (данные => 2 * данные)
.then (console.log)
.then (данные => console.log (данные))
.тогда(
() => {
выбросить новую ошибку ('Пример ошибки');
},
error => console.log (ошибка)
)
.then (() => console.log (`Я ничего не буду делать,потому что произошла ошибка`))
.тогда(
() => {},
() => {
console.log (`Однако я поймаю эту ошибку и исправлю ее,вернув 40`);
return 40;
}
)
.then (data => console.log (`Эй!У меня есть ${data}`))
.then (() => {
выбросить новую ошибку (`catch example`);
})
.catch (error => console.log (`Нет!Я являюсь оператором Catch,и я обработаю эту ошибку:${error}`))
.then (() => console.log (`Я могу присоединиться к catch,потому что catch также возвращает обещание!`));
Имейте в виду, что ошибки передаются по цепочке обещаний, и их обрабатывает:
- первый оператор
, затем
сonRejected обработчик
- первый
поймать
заявление.
Все, что будет раньше!
И небольшая диаграмма от MDN это показывает, как работает цепочка обещаний.
Считается хорошей практикой завершать цепочку обещаний оператором catch!
Обещания и обратные вызовы
Обещания лучше обратных вызовов? В некотором смысле, но на этот вопрос нет простого ответа. Обещания используют обратные вызовы,
мы это видели, мы написали код - , затем
принимает 2 обратных вызова, Конструктор Promise
принимает 2 обратных вызова.Иногда мы
нет выбора, выборка
возвращает обещание, XHR использует события, поэтому косвенные обратные вызовы. Конечно, мы можем написать
код на основе обещаний в обратном вызове или напишите код на основе обратного вызова внутри обещания. Однако я бы сказал, что это не очень хорошо
На практике смешивание этих двух парадигм в одном коде не закончится хорошо, потому что это приведет только к еще большей неразберихе внутри.
Обычно обратные вызовы сдвигают наш код вправо, создают множество ветвей и глубокое вложение (ад обратного вызова, иначе пирамида гибели) но есть способы уменьшить это!
Promises, с другой стороны, помогают нам писать более линейный код благодаря механизму связывания.Однако возможно писать обещание ад, но обычно это результат незнания обещаний.
getUsersIDs (). Then (userIDs => {
return getUserName (userIDs [0]). then (userData => {
return getUsersByName (userData.name) .then (users => console.log (`Есть пользователи ${users.length}с таким именем`));
});
});
Вот способ, как исправить пример с обещанием ада.
getUsersIDs ()
.then (userIDs => getUserData (userIDs [0]))
.затем (userData => getUsersByName (userData.name))
.then (users => console.log (`Есть ${users.length}пользователей с таким именем`));
Есть еще один недостаток callback'ов - обработка ошибок. При использовании обратных вызовов необходимо добавить еще один обратный вызов для обработки
или используйте в коде тонны операторов if
, чтобы проверить, была ли ошибка. Поэтому уследить за ним сложно.
Эта проблема немного проще при написании кода на основе обещаний. Promise API дает нам две функции: , затем
с его
на отклоненном параметре
, а перехватывает
.С ними намного проще рассуждать об обработке ошибок!
Обещания имеют 2 больших преимущества. Мы можем написать функцию, которая возвращает обещание, к которому мы можем прикрепить обработчик , затем
,
мы также можем присоединить несколько , а затем
к одному обещанию.
function getPromise () {
вернуть Promise.resolve (20);
}
const Promise = getPromise ();
обещание.then (данные => console.log (данные));
обещание.then (данные => console.log (2 * данные));
И , и
будут выполнены, как только будет выполнено обещание
.
Если бы мне пришлось выбирать между обратными вызовами и обещаниями, я бы предложил написать код, основанный на обещаниях. Мне легче
следуйте потоку кода, обычно каждый , затем
находится один под другим, и тем самым мы избегаем проблем со слишком глубоким вложением.
Заключение
Я надеюсь, что теперь обещания больше не таят для вас загадок. Однако, если вы заинтересованы в дальнейшем чтении, я настоятельно рекомендую прочитать У нас проблема с обещаниями pouchDB. Этот Статья очень помогла мне разобраться в этой сложной теме.
Как всегда, когда вы попадаете в ситуацию, когда метод работает не так, как вы ожидаете, не бойтесь изучить документацию или MDN.
Следующая остановка в нашем путешествии сегодня…
Генераторы
- Подождите ... что? Что общего у генераторов с асинхронными функциями?
Поскольку генераторы имеют особую возможность приостанавливать свое выполнение, и это то, что мы хотим делать во время обработки async. функции.
Сначала о главном:
Какие бывают генераторы?
Генераторы - это новый тип JS-функции, представленный в ES6.У них есть особые способности, но об этом чуть позже.
Как создать функцию генератора? Это просто.
function * myAwesomeGeneratorFunction () {
}
и все. Эта маленькая звездочка и наша функция - генератор.
Хорошо, но зачем нам создавать генераторы? Как я сказал в начале, это потому, что у них есть суперсила - они может… приостановить исполнение. Плотина, плотина, плотина! Разве это не здорово? Мы можем приостановить функцию и вернуться к ней через некоторое время и возобновить его выполнение.
Как это работает?
function * myAwesomeGeneratorFunction () {
console.log («Выполняется функция»);
урожай;
console.log ('функция возобновляется');
}
константный итератор = myAwesomeGeneratorFunction ();
let result = iterator.next ();
console.log (результат);
результат = итератор.next ();
console.log (результат);
результат = итератор.next ();
console.log (результат);
через Imgflip GIF Maker
Будет ли работать следующий пример?
function * myAwesomeGeneratorFunction () {
возврат 20;
возврат 30;
return 40;
}
константный итератор = myAwesomeGeneratorFunction ();
итератор.следующий();
iterator.next ();
iterator.next ();
Нет больше данных, выполнение функции завершено после первого возврата. Я солгал тебе? Нет. К сожалению, это
так не работает. Как мы можем этого добиться? Нам нужно использовать наше специальное ключевое слово - yield
. Посмотрим, что на
следующий пример.
function * myAwesomeGeneratorFunction () {
выход 20;
выход 30;
yield 40;
}
константный итератор = myAwesomeGeneratorFunction ();
iterator.next ();
итератор.следующий();
iterator.next ();
iterator.next ();
Есть небольшая разница между функцией чистовой обработки с использованием , доход
и , доход
.
function * myAwesomeGeneratorFunction () {
выход 20;
выход 30;
return 40;
}
константный итератор = myAwesomeGeneratorFunction ();
iterator.next ();
iterator.next ();
iterator.next ();
iterator.next ();
Единственная разница заключается в значении done: true
. return
возвращает значение и устанавливает для done
значение true, в отличие от
выход
.Интересный вопрос: что это означает?
function * myGenerator () {
выход 20;
выход 30;
return 40;
}
константный итератор = myGenerator ();
console.log (... итератор);
Где наши 40? Его «съели». Можно сказать, что оператор распространения «вызывает» следующий метод
и возвращает значения до тех пор, пока
сделано
это верно
. Что происходит, как мы уже знаем, когда мы вызываем и возвращаем
.
Давайте посмотрим, что произойдет, если мы используем yield
вместо return
.
function * myGenerator () {
выход 20;
выход 30;
yield 40;
}
константный итератор = myGenerator ();
console.log (... итератор);
Теперь все работает!
Возврат значения обратно в генератор
У Yield есть еще одна суперспособность, с ее помощью можно вернуть значение обратно в функцию:
function * generateAddFunction () {
const first = yield;
const second = yield;
console.log (первый + второй);
}
константный итератор = generateAddFunction ();
итератор.следующий();
iterator.next (42);
iterator.next (23);
Объединение знаний
Теперь вы знаете, что генераторы могут приостанавливать выполнение функции и возвращать значение обратно функции. Можете ли вы представить объединяя генераторы и обещания? Позвольте мне показать вам кое-что:
function * processDummyToDo (id) {
const dummyUrl = `https:const data=yield fetch(dummyUrl);console.log(данные);}
const итератор=processDummyToDo(1);итератор.следующий().ценить.then(ответ=>response.json()).then(данные=>итератор.next(данные));
Теперь это кажется знакомым?Да!Это похоже наasync/await
,что является нашей последней остановкой на сегодня.
Небольшая заметка,это основы генераторов,в них есть гораздо больше,и я призываю вас узнать больше о их!Например,здесь:https:
Асинхронный/Ожидание
Это хорошо известный синтаксис,представленный в ES7.Его главное преимущество заключается в том,что мы можем приостановить выполнение функции и подождите,пока обещание разрешится.Практически то же самое,что мы видели в последнем примере с генераторами.Самый большой Преимущество этого по сравнению с обещаниями в том,что он содержит немного синтаксического сахара.В случае обещаний,когда мы хотите что-то сделать с данными из асинхронной функции,нам нужно связать другое обещание,потому что другого пути нет чтобы двигатель дождался результатов.
Давайте углубимся в эту функцию.
Ожидание
Специальное ключевое слово,которое приостанавливает выполнение функции до тех пор,пока обещание,которое она ожидает,не будет выполнено.Вы заметили волшебное слово I
использовал?Обещание
.Async/Await
работает с обещаниями и только с обещаниями.Вы можете толькождать
разрешения
обещания.Вот что я имею в виду,говоря,что обещания-это основа современного асинхронного JS.
const response=await fetch('https://jsonplaceholder.typicode.com/todos/1');let data=ждать ответа.json();console.log(данные);data=await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res=>res.json());console.log(данные);
Это не последний пример!Пока не сработает.Думаю,вы знаете почему,но позвольте мне вас удивить.
Я считаю это решение удобным и гораздо более элегантным,чем пример с генераторами.
Помните-await
работает только с обещаниями!
Асинхронный
Требуется еще одно специальное ключевое слово-иначе мы бы не называли этот синтаксисasync/await
.Если вы хотите использовать,подождите
ключевое слово,вам нужно обернуть эту функцию вasync
.Это закон.Это потому,что вам нужно как-то сообщить движку JS
что эта функция асинхронная,и вы можете использоватьawait
внутри.
Итак,рабочий пример из предыдущего раздела будет выглядеть так:
async function asyncFunction(){data=await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res=>res.json());console.log(данные);}
asyncFunction();
Рассмотрим более сложный пример
асинхронная функция getDummyToDo(id){const data=await fetch(`https: // jsonplaceholder.typicode.com/todos/${id}`).then(response=>response.json());console.log('getDummyToDo',данные);вернуть данные;}
function processDummyToDo(id){const toDo=getDummyToDo(id);console.log('processDummyToDo',toDo);}
processDummyToDo(1);
У вас есть идеи,что не так в этом примере?Поехали шаг за шагом!
- Мы вызываем функцию
processDummyToDo
с1
в качестве аргумента. processDummyToDo
вызоветgetDummyToDo
с тем же аргументом.getDummyToDo
вызываетfetch
и ждет его разрешения.- Поскольку инициатор не является асинхронной функцией,JS не может приостановить ее выполнение.Вот почему
getDummyToDo
возвращает обещать. processDummyToDo
назначает это обещание переменнойtoDo
toDo Переменная
выводится на консоль-processDummyToDo Promise{<ожидает>}
получить
обещание разрешено- processDummyToDo печатает-
getDummyToDo{userId:1,id:1,title:"delectus aut autem",completed:false}
на консоль. getDummyToDo
возвращаетtoDo
.- Promise,первоначально возвращенный
getDummyToDo
разрешается с возвращенным из него значением.
Рабочий пример будет выглядеть следующим образом
асинхронная функция getDummyToDo(id){const data=await fetch(`https://jsonplaceholder.typicode.com/todos/$ {id}`).then(response=>response.json());console.log('getDummyToDo',данные);вернуть данные;}
асинхронная функция processDummyToDo(id){const toDo=ждать getDummyToDo(id);консоль.журнал('processDummyToDo',toDo);}
processDummyToDo(1);
Или,используя подход обещания:
асинхронная функция getDummyToDo(id){const data=await fetch(`https://jsonplaceholder.typicode.com/todos/$ {id}`).then(response=>response.json());console.log('getDummyToDo',данные);вернуть данные;}
function processDummyToDo(id){getDummyToDo(id).then(toDo=>console.log('processDummyToDo',toDo));}
processDummyToDo(1);
Извлеченные уроки:
async
превращает все возвращаемые из него значения в обещание.- Тем не менее,асинхронность подобна болезни,она делает каждую функцию,которой она касается,асинхронной,по крайней мере,если в этом есть необходимость.использовать возвращаемое из него значение.
Что делать с ошибками?
Async/await
дает нам еще одну суперсилу,это позволяет нам снова использоватьtry/catch
!Что это значит?Если обещание мы
await отклоняется,он выдает ошибку,которую мы можем поймать.
async function asyncFunction(){пытаться{await Promise.reject('Это обещание бросает');}catch(e){консоль.log('Обещание бросили, и я его поймал -',e);}
console.log(«Нормально работаю!»);}
asyncFunction();
В результате выполнения нашего примера на консоль будут напечатаны следующие строки:
- Обещание сброшено,и я его поймал-Это обещание сбрасывает
- Работаю нормально!
Нет необходимости в заявленииPromise.catch
или в чем-то еще.Более того,в консоли нет ошибки!
Объединение с обещаниями
Но что,если вы хотите создать несколько запросов одновременно?
function constructTimeoutPromise(время,имя){вернуть новое обещание(resolve=>{setTimeout(()=>разрешение(имя),время);});}
асинхронная функция synchronousRequests(){const start=производительность.Теперь();const name1=ожидание constructTimeoutPromise(3000,'foo');const name2=ожидание constructTimeoutPromise(2000,'бар');const name3=ожидание constructTimeoutPromise(4000,'baz');console.log(«потребовалось всего»,performance.now()-начало,«мс для выполнения запросов»);}
synchronousRequests();
В конце функции мы можем увидеть,что нашей функции потребовалось более 9 секунд для завершения функции,но мы хотим вызвать их сразу,потому что эти запросы не зависят друг от друга.Как этого добиться?Обещает помочь!
function constructTimeoutPromise(время,имя){вернуть новое обещание(resolve=>{setTimeout(()=>разрешение(имя),время);});}
асинхронная функция asynchronousRequests(){const start=performance.now();const promise1=constructTimeoutPromise(3000,'foo');const Promise2=constructTimeoutPromise(2000,«бар»);const Promise3=constructTimeoutPromise(4000,'baz');const data=await Promise.all([обещание1,обещание2,обещание3]);консоль.log('потребовалось всего',performance.now()-начало,'мс на выполнение запросов');console.log(данные);}
asynchronousRequests();
И теперь все работает так,как мы хотели!Предыдущий пример приведет к печати:
- потребовалось всего 4002,859999993816 мс для выполнения запросов
- (3)["foo","bar","baz"]-Как вы помните,
Promise.