Асинхронность js: Асинхронный JavaScript — Изучение веб-разработки

Содержание

Асинхронный 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 или телеграмм чат, а также подписывайтесь на наши новости

Вопросы:#

Асинхронность — это:

  1. Инструмент, который выводит контекст исполнения функции из синхронного потока
  2. Инструмент, который исполняет код построчно
  3. Инструмент, который обрабатывает запросы параллельно с загрузкой веб-страниц

Менеджер асинхронных вызовов:

  1. stack
  2. Event loop
  3. Объекты высшего класса

Инструмент, выполняющий код с задержкой в миллисекундах:

  1. delay
  2. heap
  3. setTimeout

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

Ссылки:#

  1. Объяснение работы EventLoop в JavaScript
  2. Как управлять event loop в JavaScript
  3. Справочник javascript
  4. Статья: Объяснение Event Loop в Javascript с помощью визуализации
  5. Статья: 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 () она вызывает предоставленный вами обработчик. Пока приложение не предполагает, что потоковая передача началась, оно может просто продолжать работать.

Примечание: Вы можете узнать больше об этой несколько сложной теме, если вам интересно, в статье Сигнализация и видеозвонки. В этом примере используется код, похожий на этот, но гораздо более полный.

Чтобы полностью понять, почему обещания — это хорошо, полезно вспомнить старые обратные вызовы и понять, почему они проблематичны.

Давайте поговорим о заказе пиццы по аналогии. Есть определенные шаги, которые вы должны предпринять для того, чтобы ваш заказ был успешным, что на самом деле не имеет смысла пытаться выполнить не по порядку или по порядку, но до того, как каждый предыдущий шаг будет полностью завершен:

  1. Вы сами выбираете, какую начинку хотите.Это может занять некоторое время, если вы нерешительны, и может потерпеть неудачу, если вы просто не можете принять решение или вместо этого решите купить карри.
  2. Затем вы размещаете свой заказ. Это может занять некоторое время, чтобы вернуть пиццу, и может не получиться, если в ресторане нет необходимых ингредиентов для ее приготовления.
  3. Затем вы собираете пиццу и едите. Это может потерпеть неудачу, если, скажем, вы забыли свой кошелек и не можете заплатить за пиццу!

При использовании обратных вызовов в старом стиле псевдокодовое представление вышеуказанной функциональности может выглядеть примерно так:

  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.

  1. Прежде всего, загрузите наш простой шаблон HTML и образец файла изображения, который мы получим.

  2. Добавьте элемент .

  3. Загрузите наши исходные файлы (coffee.jpg, tea.jpg и description.txt) или замените их своими собственными.

  4. В нашем скрипте мы сначала определим функцию, которая возвращает обещания, которые мы хотим отправить на Promise.all () . Это было бы легко, если бы мы просто хотели запустить блок Promise.all () в ответ на завершение трех операций fetch () . Мы могли бы просто сделать что-то вроде:

      пусть a = выборка (url1);
    пусть b = выборка (url2);
    пусть c = выборка (url3);
    
    Обещать.all ([a, b, c]). then (values ​​=> {
      ...
    });  

    Когда обещание выполнено, значения , переданные в обработчик выполнения, будут содержать три объекта Response , по одному для каждой из завершенных операций fetch () .

    Однако мы не хотим этого делать. Нашему коду безразлично, когда будут выполнены операции fetch () . Вместо этого нам нужны загруженные данные. Это означает, что мы хотим запустить блок Promise.all () , когда мы получим обратно пригодные для использования большие двоичные объекты, представляющие изображения, и используемую текстовую строку.Мы можем написать функцию, которая сделает это; добавьте в элемент