Рубрики
Без рубрики

Node.js async a a ждать учебника – с асинхронными примерами JavaScript

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

Автор оригинала: FreeCodeCamp Community Member.

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

Так сложно, как следует забрать, Async-программирование имеет решающее значение для изучения, если вы хотите использовать JavaScript и Node.js для создания веб-приложений и серверов – Поскольку код JS – асинхронный по умолчанию Отказ

Асинхронные программированные основы

Так что же именно асинхронная обработка модель или не блокировка ввода/вывода Модель (которую вы, вероятно, слышали о если вы являетесь пользователем Node.js)?

Вот TL; DR Описание: В модели обработки Async, когда ваш прикладной двигатель взаимодействует с внешними сторонами (например, файловая система или сеть), она не дождается до тех пор, пока не получит результат от этих вечеринок. Вместо этого он продолжается до последующих задач, и только возвращается к этим предыдущим внешним сторонам после того, как она получила сигнал результата.

Чтобы понять модель обработки асинхронизации по умолчанию Node.js, давайте посмотрим на гипотетический семинар Санты. Перед началом работы Санта придется прочитать каждую из прекрасных букв от детей во всем мире.

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

В этом году из-за COVID-19 пандемия , только половина эльфов Санта может прийти к его мастерской, чтобы помочь. Тем не менее, потому что он мудрый, Санта решает, что вместо того, чтобы ждать каждого эльфа, чтобы закончить готовить подарок (то есть, работает синхронно), он продолжит переводить и превышать инструкции из своей кучи букв.

Так далее и тому подобное…

Когда он почти прочитал другое письмо, Red Info сообщает Санта, что он завершил подготовку первого подарка. Затем Санта получает настоящее из красного и положит в одну сторону.

И тогда он продолжает переводить и проходить инструкции из следующего письма.

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

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

Так что это основная идея асинхронной или неблокирующей модели обработки ввода/вывода. Теперь давайте посмотрим, как это сделано в Node.js конкретно.

Структура событий Node.js

Возможно, вы слышали, что Node.js – однопоточный. Однако, чтобы быть точным, только контур событий в Node.js, который взаимодействует с пулом фона рабочие потоки C ++, является однопоточным. Существует четыре важных компонента в модели обработки Node.js:

  • Очередь события: задачи, которые объявлены в программе или возвращены из пула обработки нитей через Обратные вызовы Отказ (Эквивалент этого в нашем мастерской Санта – это куча писем для Санта.)
  • Структура событий: основной поток Node.js, который облегчает очереди событий и пулы на рабочих потоках для выполнения операций – как асинхрониза и синхронно. (Это Санта. 🎅)
  • Фоновая резьба пула: эти потоки делают фактическую обработку задач, которые Может быть блокировкой ввода/вывода (например, вызов и ожидание ответа от внешнего API). (Это трудолюбивые эльфы 🧝🧝♀️🧝♂️ от нашей мастерской.)

Вы можете визуализировать эту модель обработки, как показано ниже:

Давайте посмотрим на фактический фрагмент кода, чтобы увидеть их в действии:

console.log("Hello");
https.get("https://httpstat.us/200", (res) => {
  console.log(`API returned status: ${res.statusCode}`);
});
console.log("from the other side");

Если мы выполним вышеуказанный кусок кода, мы получим это на нашем стандартном выходе:

Hello
from the other side
API returned status: 200

Итак, как двигатель Node.js выполняет вышеупомянутый фрагмент кода? Он начинается с трех функций в стеке вызовов:

«Hello» затем печатается на консоль с соответствующим вызовом функций, удаленным из стека.

Функция звонит на https.get (То есть создание запроса на получение соответствующего URL-адреса) затем выполняется и делегирована в пул рабочего потока с прикрепленным обратным вызовом.

Следующая функция звонка console.log Выполняется, а «с другой стороны» напечатано на консоль.

Теперь, когда сетевой звонок вернул ответ, вызов функции обратного вызова затем попадет в очередь в очереди обратного вызова. Обратите внимание, что этот шаг может произойти до немедленного предыдущего шага (то есть «с другой стороны» напечатаны), хотя обычно это не так.

Затем обратный вызов встает внутри нашего стека вызовов:

И тогда мы увидим, что «API вернул статус: 200» в нашей консоли, как это:

Соблюдая очередь обратного вызова и стека вызовов, контур событий в Node.js эффективно выполняет наш код JavaScript в асинхронном виде.

Синхронная история JavaScript и Node.js async/ждут

Теперь, когда у вас есть хорошее понимание асинхронного казни и внутренних ведений контура событий Node.js, давайте погрузимся в Async/ждут в JavaScript. Мы посмотрим на то, как он работает через время, от оригинального обратного вызовов реализации до последних ключевых слов блестящих async/await.

Обратные вызовы в JavaScript

Отель OG по обращению с асинхронной природой двигателей JavaScript был через обратные вызовы. Обратные вызовы в основном функционируют, которые будут выполнены, Обычно в конце синхронных или ввода/вывода блокирующих операций.

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

setTimeout(2000, () => {
  console.log("Hello");
});

Хотя удобно просто прикреплять обратные вызовы для блокировки операций, этот шаблон также представляет пару проблем:

  • Обратный ад
  • Инверсия контроля (не хороший вид!)

Что такое обратный черт ада?

Давайте посмотрим на пример с Санта и его эльфами снова. Чтобы подготовить подарок, семинар Санты должен будет проводить несколько разных шагов (с каждым принять разные суммы, смоделированные с использованием SettimeOut ):

function translateLetter(letter, callback) {
  return setTimeout(2000, () => {
    callback(letter.split("").reverse().join(""));
  });
}
function assembleToy(instruction, callback) {
  return setTimeout(3000, () => {
    const toy = instruction.split("").reverse().join("");
    if (toy.includes("wooden")) {
      return callback(`polished ${toy}`);
    } else if (toy.includes("stuffed")) {
      return callback(`colorful ${toy}`);
    } else if (toy.includes("robotic")) {
      return callback(`flying ${toy}`);
    }
    callback(toy);
  });
}
function wrapPresent(toy, callback) {
  return setTimeout(1000, () => {
    callback(`wrapped ${toy}`);
  });
}

Эти шаги должны быть выполнены в определенном порядке:

translateLetter("wooden truck", (instruction) => {
  assembleToy(instruction, (toy) => {
    wrapPresent(toy, console.log);
  });
});
// This will produced a "wrapped polished wooden truck" as the final result

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

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

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

function assembleCb(toy) {
  wrapPresent(toy, console.log);
}
function translateCb(instruction) {
  assembleToy(instruction, assembleCb);
}
translateLetter("wooden truck", translateCb);

Инверсия контроля

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

По сути, вы находитесь в милосердии своих владельцев зависимостей, и вы никогда не узнаете, когда они сломают ваш код.

Чтобы решить эту проблему, как пользователь зависимости, не так много, вы можете с этим поделать. Однако, если вы когда-нибудь являетесь владельцем зависимостей самостоятельно, пожалуйста, всегда:

  • Придерживаться обычной подписи обратного вызова с ошибкой как первый аргумент
  • Выполните обратный вызов только один раз в конце своей функции высшего порядка
  • Документе что-нибудь вне конвенции, которое абсолютно требуется и всегда стремится к обратной совместимости

Обещания в JavaScript

Обещания были созданы для решения этих вышеупомянутых проблем с обратными вызовами. Обежки Убедитесь, что пользователи JavaScript:

  • Придерживайтесь конкретной конвенции со своей подписью решить и Отклонить Функции.
  • Цепочка обратного вызова функционирует к хорошо выровненному и сверху вниз.

Наш предыдущий пример с семинаром Санта, подготовка настоящих предложений, может быть переписана, подобные обещаниям:

function translateLetter(letter) {
  return new Promise((resolve, reject) => {
    setTimeout(2000, () => {
      resolve(letter.split("").reverse().join(""));
    });
  });
}
function assembleToy(instruction) {
  return new Promise((resolve, reject) => {
    setTimeout(3000, () => {
      const toy = instruction.split("").reverse().join("");
      if (toy.includes("wooden")) {
        return resolve(`polished ${toy}`);
      } else if (toy.includes("stuffed")) {
        return resolve(`colorful ${toy}`);
      } else if (toy.includes("robotic")) {
        return resolve(`flying ${toy}`);
      }
      resolve(toy);
    });
  });
}
function wrapPresent(toy) {
  return new Promise((resolve, reject) => {
    setTimeout(1000, () => {
      resolve(`wrapped ${toy}`);
    });
  });
}

С ступенями, проведенными красиво в цепочке:

translateLetter("wooden truck")
  .then((instruction) => {
    return assembleToy(instruction);
  })
  .then((toy) => {
    return wrapPresent(toy);
  })
  .then(console.log);
// This would produce the exact same present: wrapped polished wooden truck

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

Например, наша стадия упаковки подарков может захотеть использовать данные из шага перевода:

function wrapPresent(toy, instruction) {
  return Promise((resolve, reject) => {
    setTimeout(1000, () => {
      resolve(`wrapped ${toy} with instruction: "${instruction}`);
    });
  });
}

Это скорее Классическая проблема «Обмен памяти» с Threading Отказ Чтобы решить это, вместо использования переменных в области родительского пола, мы должны использовать Обещание. Все и «Поделиться данными путем общения, вместо того, чтобы обмениваться с помощью данных» Отказ

translateLetter("wooden truck")
  .then((instruction) => {
    return Promise.all([assembleToy(instruction), instruction]);
  })
  .then((toy, instruction) => {
    return wrapPresent(toy, instruction);
  })
  .then(console.log);
// This would produce the present: wrapped polished wooden truck with instruction: "kcurt nedoow"

Async/ждут в JavaScript

Последнее, но определенно не менее важно, самый блестный ребенок вокруг блока – async/ждут. Он очень прост в использовании, но он также имеет некоторые риски.

Async/a ждать решает проблемы обмена памятью обещаний, имея все под одной и той же области. Наш предыдущий пример можно легко переписать так:

(async function main() {
  const instruction = await translateLetter("wooden truck");
  const toy = await assembleToy(instruction);
  const present = await wrapPresent(toy, instruction);
  console.log(present);
})();
// This would produce the present: wrapped polished wooden truck with instruction: "kcurt nedoow"

Тем не менее, столько, сколько легко писать асинхронный код с async/a ждать, также легко ошибиться, которые создают производительные лазейки.

Давайте теперь локализуем наш пример сценария семинара Санты для упаковки подарков и загрузки их на сани.

function wrapPresent(toy) {
  return Promise((resolve, reject) => {
    setTimeout(5000 * Math.random(), () => {
      resolve(`wrapped ${toy}`);
    });
  });
}
function loadPresents(presents) {
  return Promise((resolve, reject) => {
    setTimeout(5000, () => {
      let itemList = "";
      for (let i = 0; i < presents.length; i++) {
        itemList += `${i}. ${presents[i]}\n`;
      }
    });
  });
}

Общая ошибка, которую вы можете сделать, выполняет шаги таким образом:

(async function main() {
  const presents = [];
  presents.push(await wrapPresent("wooden truck"));
  presents.push(await wrapPresent("flying robot"));
  presents.push(await wrapPresent("stuffed elephant"));
  const itemList = await loadPresents(presents);
  console.log(itemList);
})();

Но нужно ли Санта? ждать Для каждого из подарков на один за другим до загрузки? Точно нет! Подарки должны быть обернуты одновременно. Вы можете сделать эту ошибку часто, так как это так легко написать ждать Не думая о блокирующей природе ключевого слова.

Чтобы решить эту проблему, мы должны объединить шаги на упаковке подарков и выполнить их все сразу:

(async function main() {
  const presents = await Promise.all([
    wrapPresent("wooden truck"),
    wrapPresent("flying robot"),
    wrapPresent("stuffed elephant"),
  ]);
  const itemList = await loadPresents(presents);
  console.log(itemList);
})();

Вот несколько рекомендуемых шагов по решению проблемы производительности параллелизма в вашем коде Node.js:

  • Определите горячие точки с несколькими последовательными ждет в вашем коде
  • Проверьте, зависит ли они друг от друга (то есть одна функция использует данные, возвращенные из другого)
  • Сделать независимые функции звонки одновременно с Обещать. Все

Упаковка (статья, а не рождественские подарки 😂)

Поздравляем с достижением конца этой статьи, я старался изо всех сил, чтобы сделать этот пост короче, но async-тема в JavaScript настолько шире.

Вот некоторые ключевые вынос:

  • Модульные обратные вызовы JavaScript, чтобы избежать обратного вызова ада
  • Придерживаться Конвенция для обратных вызовов JS
  • Поделиться данными путем общения через Обещание. Все При использовании обещаний
  • Будьте осторожны на производительности, воздействие кода Async/ждут

Мы ❤️ JavaScript:)

Спасибо за чтение!

Последнее, но не менее важное, если вам нравятся мои писания, пожалуйста, отправляйтесь на мой блог Для похожих комментариев и следуйте я в твиттере Отказ 🎉.