Автор оригинала: FreeCodeCamp Community Member.
JavaScript – странный язык. Время от времени вы должны иметь дело с обратным вызовом, который в другом обратном вызове, который в еще одном обратном вызове.
Люди ласково называют этот шаблон Обратный вызов Ад Отказ
Это выглядит так:
firstFunction(args, function() { secondFunction(args, function() { thirdFunction(args, function() { // And so on… }); }); });
Это JavaScript для вас. Очень ошеломлен, чтобы увидеть вложенные обратные вызовы, но я не думаю, что это «ад». «Ад» может быть управляемым, если вы знаете, что с этим делать.
По обратным вызовам
Я предполагаю, что вы знаете, какие обратные вызовы, если вы читаете эту статью. Если вы этого не сделаете, пожалуйста, прочитайте Эта статья для введения в обратные вызовы до продолжения. Там мы говорим о том, какие обратные вызовы есть и почему вы используете их в JavaScript.
Решения для обратного вызова ада
Есть четыре решения для обратного вызова ада:
- Написать комментарии
- Разделить функции на меньшие функции
- Используя обещания
- Используя async/ждать
Перед тем, как мы погрузимся в решения, давайте построим обратный ад вместе. Почему? Потому что это слишком абстрактно, чтобы увидеть первая функция
, Вторая информация
и третьемфункция
Отказ Мы хотим сделать его бетон.
Построение обратного вызова ада
Давайте представим, что мы пытаемся сделать гамбургер. Чтобы сделать гамбургер, нам нужно пройти через следующие шаги:
- Получить ингредиенты (мы будем думать, что это говяжий бургер)
- Готовить говядину
- Получить булочки Бургера
- Положите приготовленную говядину между булочками
- Служить бургером
Если эти шаги синхронно, вы смотрите на функцию, которая напоминает это:
const makeBurger = () => { const beef = getBeef(); const patty = cookBeef(beef); const buns = getBuns(); const burger = putBeefBetweenBuns(buns, beef); return burger; }; const burger = makeBurger(); serve(burger);
Однако в нашем сценарии скажем, мы не можем сделать бургер. Мы должны проинструктировать помощника на шагах, чтобы сделать бургер. После того, как мы поручаем помощника, мы должны Подожди Для помощника до конца мы начнем следующий шаг.
Если мы хотим дождаться чего-то в JavaScript, нам нужно использовать обратный вызов. Чтобы сделать бургер, мы должны сначала получить говядину. Мы можем готовить только говядину после того, как мы получим говядину.
const makeBurger = () => { getBeef(function(beef) { // We can only cook beef after we get it. }); };
Чтобы приготовить говядину, нам нужно пройти говядина
в Cookbeef
функция. В противном случае нечего готовить! Затем мы должны ждать говядины, чтобы приготовить.
Как только говядина будет приготовлена, мы получаем плюшки.
const makeBurger = () => { getBeef(function(beef) { cookBeef(beef, function(cookedBeef) { getBuns(function(buns) { // Put patty in bun }); }); }); };
После того, как мы получим булочки, нам нужно поставить пирожок между булочками. Это где формируется бургер.
const makeBurger = () => { getBeef(function(beef) { cookBeef(beef, function(cookedBeef) { getBuns(function(buns) { putBeefBetweenBuns(buns, beef, function(burger) { // Serve the burger }); }); }); }); };
Наконец, мы можем служить бургере! Но мы не можем вернуться бургер
от Мягбургер
потому что это асинхронно. Нам нужно принять обратный вызов для обслуживания бургера.
const makeBurger = nextStep => { getBeef(function (beef) { cookBeef(beef, function (cookedBeef) { getBuns(function (buns) { putBeefBetweenBuns(buns, beef, function(burger) { nextStep(burger) }) }) }) }) } // Make and serve the burger makeBurger(function (burger) => { serve(burger) })
(Мне было весело сделать этот обратный вызов адским примером?).
Первое решение для обратного вызова Ада: запись комментариев
Мягбургер
Обратный вызов ад просто понять. Мы можем прочитать это. Это просто … не выглядит красиво.
Если вы читаете Мягбургер
Впервые вы можете подумать «Почему, черт возьми, нам нужно так много обратных вызовов, чтобы сделать гамбургер? Это не имеет смысла!».
В таком случае вы хотите оставить комментарии, чтобы объяснить ваш код.
// Makes a burger // makeBurger contains four steps: // 1. Get beef // 2. Cook the beef // 3. Get buns for the burger // 4. Put the cooked beef between the buns // 5. Serve the burger (from the callback) // We use callbacks here because each step is asynchronous. // We have to wait for the helper to complete the one step // before we can start the next step const makeBurger = nextStep => { getBeef(function(beef) { cookBeef(beef, function(cookedBeef) { getBuns(function(buns) { putBeefBetweenBuns(buns, beef, function(burger) { nextStep(burger); }); }); }); }); };
Теперь, вместо того, чтобы думать «WTF?!» Когда вы видите обратный черт ада, вы понимаете, почему он должен быть написан таким образом.
Второе решение для обратного вызова ада: разделить обратные вызовы в разные функции
Наш обратный вызов ада пример уже приведен пример этого. Позвольте мне показать вам пошаговый императивный код, и вы увидите, почему.
Для GetBeef
Наш первый обратный вызов, мы должны пойти на холодильник, чтобы получить говядину. На кухне есть два холодильника. Нам нужно идти в правый холодильник.
const getBeef = nextStep => { const fridge = leftFright; const beef = getBeefFromFridge(fridge); nextStep(beef); };
Готовить говядину, нам нужно поставить говядину в печь; Поверните духовку до 200 градусов и подождите двадцать минут.
const cookBeef = (beef, nextStep) => { const workInProgress = putBeefinOven(beef); setTimeout(function() { nextStep(workInProgress); }, 1000 * 60 * 20); };
Теперь представьте, если вы должны написать каждый из этих шагов в Мягбургер
… Вы, вероятно, упадут от чистого количества кода!
Для конкретного примера при разделении обратных вызовов на более мелкие функции вы можете прочитать Этот маленький раздел в моей статье обратного вызова.
Третье решение для обратного вызова ада: использовать обещания
Я собираюсь полагать, что вы знаете, что есть обещания. Если вы этого не сделаете, пожалуйста Прочитайте эту статью Отказ
Обещания могут сделать обратный черт, черт возьми, намного проще управлять. Вместо вложенного кода вы видите выше, у вас будет это:
const makeBurger = () => { return getBeef() .then(beef => cookBeef(beef)) .then(cookedBeef => getBuns(beef)) .then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef)); }; // Make and serve burger makeBurger().then(burger => serve(burger));
Если вы используете преимущества стиля единого аргумента с обещаниями, вы можете настроить выше к этому:
const makeBurger = () => { return getBeef() .then(cookBeef) .then(getBuns) .then(putBeefBetweenBuns); }; // Make and serve burger makeBurger().then(serve);
Гораздо проще читать и управлять.
Но вопрос в том, как вы конвертируете код на основе обратного вызова в код на основе обещания.
Преобразование обратных вызовов на обещания
Чтобы преобразовать обратные вызовы в обещания, нам нужно создать новое обещание для каждого обратного вызова. Мы можем решить
обещание, когда обратный вызов успешен. Или мы можем Отклонить
Обещание, если обратный вызов терпит неудачу.
const getBeefPromise = _ => { const fridge = leftFright; const beef = getBeefFromFridge(fridge); return new Promise((resolve, reject) => { if (beef) { resolve(beef); } else { reject(new Error("No more beef!")); } }); }; const cookBeefPromise = beef => { const workInProgress = putBeefinOven(beef); return new Promise((resolve, reject) => { setTimeout(function() { resolve(workInProgress); }, 1000 * 60 * 20); }); };
На практике обратные вызовы, вероятно, будут написаны для вас уже. Если вы используете узел, каждая функция, которая содержит обратный вызов, будет иметь тот же синтаксис:
- Обратный вызов был бы последним аргументом
- Обратный вызов всегда будет иметь два аргумента. И эти аргументы в том же порядке. (Ошибка сначала, а затем все, что вас интересует).
// The function that's defined for you const functionName = (arg1, arg2, callback) => { // Do stuff here callback(err, stuff); }; // How you use the function functionName(arg1, arg2, (err, stuff) => { if (err) { console.error(err); } // Do stuff });
Если ваш обратный вызов имеет тот же синтаксис, вы можете использовать библиотеки, такие как ES6 похищает или Деноделификация (De-Node-Ify) этот обратный вызов в обещание. Если вы используете Node V8.0 и выше, вы можете использовать Util.promisify Отказ
Все три из них работают. Вы можете выбрать любую библиотеку для работы с. Есть небольшие нюансы между каждым методом, хотя. Я оставлю вас, чтобы проверить их документацию для How-Tos.
Четвертое решение для обратного вызова ада: используйте асинхронные функции
Чтобы использовать асинхронные функции, вам нужно сначала знать две вещи:
- Как конвертировать обратные вызовы в обещания (чтение выше)
- Как использовать асинхронные функции ( Прочитайте это Если вам нужна помощь).
С асинхронными функциями вы можете написать Мягбургер
Как будто это снова синхронно!
const makeBurger = async () => { const beef = await getBeef(); const cookedBeef = await cookBeef(beef); const buns = await getBuns(); const burger = await putBeefBetweenBuns(cookedBeef, buns); return burger; }; // Make and serve burger makeBurger().then(serve);
Есть одно улучшение, которое мы можем сделать в Мягбургер
здесь. Вы, вероятно, можете получить два помощника на GetBuns
и GetBeef
в то же время. Это означает, что вы можете ждать
они оба с Обещание. Все
Отказ
const makeBurger = async () => { const [beef, buns] = await Promise.all(getBeef, getBuns); const cookedBeef = await cookBeef(beef); const burger = await putBeefBetweenBuns(cookedBeef, buns); return burger; }; // Make and serve burger makeBurger().then(serve);
(Примечание: вы можете сделать то же самое с обещаниями … но синтаксис не так хорош и такой четкий, как функции async/a ждать).
Обертывание
Обратный вызов ад не так ад, как вы думаете. Есть четыре простых способа управления обратным вызовом ада:
- Написать комментарии
- Разделить функции на меньшие функции
- Используя обещания
- Используя async/ждать
Эта статья была первоначально опубликована на мой блог Отказ Подпишитесь на мой рассылка Если вы хотите больше статей, чтобы помочь вам стать лучшим разработчиком Frontend.