Как да се справите с вложените обратни повиквания и да избегнете „адски обратно повикване“

Снимка на Джеферсън Сантос на Unsplash

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

Хората любезно наричат ​​този модел обратния ад.

Изглежда така:

firstFunction (args, function () {
  secondFunction (args, function () {
    третаФункция (args, function () {
      // И така нататък…
    });
  });
});

Това е JavaScript за вас. Удивително е да виждате вложени входящи повиквания, но не мисля, че това е „ад“. „Адът“ може да бъде управляем, ако знаете какво да правите с него.

При обратни повиквания

Предполагам, че знаете какви са обратните обаждания, ако четете тази статия. Ако не го направите, моля, прочетете тази статия за въведение в обратните повиквания, преди да продължите. Там говорим за това какви са обратните повиквания и защо ги използвате в JavaScript.

Решения за ада обратно

Има четири решения за ада на обратно повикване:

  1. Пишете коментари
  2. Разделяне на функции на по-малки функции
  3. Използване на обещания
  4. Използване на Async / чакайте

Преди да се потопим в решенията, нека заедно да изградим адски обратни обаждания. Защо? Тъй като е твърде абстрактно, за да видите firstFunction, secondFunction и третоFunction. Искаме да го направим конкретен.

Конструиране на ада обратно

Нека си представим, че се опитваме да направим бургер. За да направим бургер, трябва да преминем през следните стъпки:

  1. Вземете съставки (предполагаме, че е говеждо бургер)
  2. Гответе говеждото месо
  3. Вземете кифлички за бургер
  4. Поставете свареното говеждо месо между кифличките
  5. Сервирайте бургер

Ако тези стъпки са синхронни, ще разгледате функция, която прилича на тази:

const makeBurger = () => {
  const beef = getBeef ();
  const patty = готви Beeef (говеждо);
  const buns = getBuns ();
  const burger = putBeefBetweenBuns (кифли, говеждо);
  връщане бургер;
};
const burger = makeBurger ();
служи (бургер);

В нашия сценарий обаче нека кажем, че не можем сами да си направим бургер. Трябва да инструктираме помощник по стъпките, за да направим бургер. След като инструктираме помощника, трябва да ЧАКАМЕ помощника да приключи, преди да започнем следващата стъпка.

Ако искаме да изчакаме нещо в JavaScript, трябва да използваме обратно извикване. За да направим бургер, първо трябва да вземем говеждото месо. Можем да готвим говеждото само след като получим говеждото.

const makeBurger = () => {
  getBeef (функция (говеждо) {
    // Можем да готвим говеждо месо само след като го получим.
  });
};

За да готвим говеждото месо, трябва да преминем говеждото във функцията CookBeef. Иначе няма какво да готвим! След това трябва да изчакаме говеждото да се сготви.

След като готвенето говеждо месо, получаваме кифлички.

const makeBurger = () => {
  getBeef (функция (говеждо) {
    CookBeef (говеждо месо, функция (CookBeef) {
      getBuns (функция (кифлички) {
        // Поставете баничка в кифличка
      });
    });
  });
};

След като вземем кифличките, трябва да поставим баницата между кифличките. Тук се образува бургер.

const makeBurger = () => {
  getBeef (функция (говеждо) {
    CookBeef (говеждо месо, функция (CookBeef) {
      getBuns (функция (кифлички) {
        putBeefBet BetweenBuns (кифли, говеждо месо, функция (бургер) {
            // Сервирайте бургер
        });
      });
    });
  });
};

Накрая можем да сервираме бургер! Но не можем да върнем бургер от makeBurger, защото той е асинхронен. Трябва да приемем обратно извикване, за да обслужваме бургер.

const makeBurger = nextStep => {
  getBeef (функция (говеждо) {
    CookBeef (говеждо месо, функция (CookBeef) {
      getBuns (функция (кифлички) {
        putBeefBet BetweenBuns (кифли, говеждо месо, функция (бургер) {
          NeXTSTEP (бургер)
        })
      })
    })
  })
}
// Направете и сервирайте бургер
makeBurger (функция (бургер) => {
  служи (бургер)
})

(Забавлявах се, като направих този пример за обратно извикване hell).

Първо решение за обратния разговор: Напишете коментари

Адът за обратно извикване на makeBurger е лесен за разбиране. Можем да го прочетем. Просто ... не изглежда хубаво

Ако четете makeBurger за първи път, може да си помислите „Защо, по дяволите, ни трябват толкова много обратни повиквания, за да направим бургер? Няма смисъл! ”.

В такъв случай бихте искали да оставяте коментари, за да обясните кода си.

// Прави бургер
// makeBurger съдържа четири стъпки:
// 1. Вземете говеждо месо
// 2. Гответе говеждото месо
// 3. Вземете кифлички за бургер
// 4. Поставете свареното говеждо месо между кифличките
// 5. Сервирайте бургер (от обратно повикване)
// Ние използваме обратни повиквания тук, защото всяка стъпка е асинхронна.
// Трябва да изчакаме помощникът да изпълни една стъпка
// преди да можем да започнем следващата стъпка
const makeBurger = nextStep => {
  getBeef (функция (говеждо) {
    CookBeef (говеждо месо, функция (CookBeef) {
      getBuns (функция (кифлички) {
        putBeefBet BetweenBuns (кифли, говеждо месо, функция (бургер) {
          NeXTSTEP (бургер);
        });
      });
    });
  });
};

Сега, вместо да мислите „wtf ?!“, когато видите ада обратно, вие разбирате защо трябва да бъде написана по този начин.

Второ решение за ада на обратно повикване: Разделете обратните повиквания на различни функции

Нашият пример за обратно обратно извикване вече е пример за това. Нека ви покажа императивния код стъпка по стъпка и ще видите защо.

За getBeef, първият ни обратен сигнал, трябва да отидем до хладилника, за да вземем говеждото. В кухнята има два хладилника. Трябва да отидем до правилния хладилник.

const getBeef = nextStep => {
  const хладилник = leftFright;
  const beef = getBeefFromFridge (хладилник);
  NeXTSTEP (говеждо);
};

За да готвим говеждо месо, трябва да поставим говеждото във фурна; включете фурната на 200 градуса и изчакайте двадесет минути.

const cookBeef = (говеждо месо, следващ стъпка) => {
  const workInProgress = putBeefinOven (говеждо);
  setTimeout (функция () {
    NeXTSTEP (workInProgress);
  }, 1000 * 60 * 20);
};

А сега си представете, ако трябва да напишете всяка от тези стъпки в makeBurger ... вероятно ще припаднете от чистото количество код!

За конкретен пример за разделяне на обратни повиквания на по-малки функции, можете да прочетете този малък раздел в моята статия за обратно извикване.

Трето решение за ада на обратно повикване: Използвайте обещания

Предполагам, че знаете какви са обещанията. Ако не го направите, моля, прочетете тази статия.

Обещанията могат да направят адски обратно повикване много по-лесно за управление. Вместо вложен код, който виждате по-горе, ще имате това:

const makeBurger = () => {
  връщане getBeef ()
    . след това (говеждо месо => готвач Бей (говеждо))
    . след това (CookBeef => getBuns (говеждо))
    .then (bunsAndBeef => putBeefBetweenBuns (bunsAndBeef));
};
// Направете и сервирайте бургер
makeBurger (). тогава (burger => serve (burger));

Ако се възползвате от стила на един аргумент с обещания, можете да настроите горното към това:

const makeBurger = () => {
  връщане getBeef ()
    .След (cookBeef)
    .След (getBuns)
    .След (putBeefBetweenBuns);
};
// Направете и сервирайте бургер
makeBurger () след това (служи).

Много по-лесно за четене и управление.

Въпросът е как да конвертирате базиран на извикване код в код, базиран на обещания.

Преобразуване на обратни повиквания в обещания

За да преобразуваме обратните повиквания в обещания, трябва да създадем ново обещание за всеки обратен сигнал. Можем да разрешим обещанието, когато обратното обаждане е успешно. Или можем да отхвърлим обещанието, ако обратният разговор не успее.

const getBeefPromise = _ => {
  const хладилник = leftFright;
  const beef = getBeefFromFridge (хладилник);
  върнете ново обещание ((разрешаване, отхвърляне) => {
    ако (говеждо) {
      решаване (говеждо месо);
    } else {
      отхвърлете (нова грешка („Няма повече говеждо!“));
    }
  });
};
const CookBeefPromise = говеждо => {
  const workInProgress = putBeefinOven (говеждо);
  върнете ново обещание ((разрешаване, отхвърляне) => {
    setTimeout (функция () {
      решителност (workInProgress);
    }, 1000 * 60 * 20);
  });
};

На практика обратните обаждания вероятно вече ще бъдат писани за вас. Ако използвате Node, всяка функция, която съдържа обратен сигнал, ще има същия синтаксис:

  1. Обратното извикване ще бъде последният аргумент
  2. Обратното извикване винаги ще има два аргумента. И тези аргументи са в същия ред. (Първа грешка, последвана от всичко, което ви интересува).
// Функцията, която е определена за вас
const functionName = (arg1, arg2, обратно извикване) => {
  // Направете неща тук
  обратен сигнал (грешка, неща);
};
// Как използвате функцията
functionName (arg1, arg2, (грешка, неща) => {
  ако (грешка) {
  console.error (ERR);
  }
  // Правете неща
});

Ако обратният ви разговор има същия синтаксис, можете да използвате библиотеки като ES6 Promisify или Denodeify (de-node-ify), които обратно повикване да бъдат обещани. Ако използвате Node v8.0 и по-нова версия, можете да използвате util.promisify.

И трите работят. Можете да изберете всяка библиотека, с която да работите. Има обаче малки нюанси между всеки метод. Ще ви оставя да проверите тяхната документация за това.

Четвърто решение за ада на обратно повикване: Използвайте асинхронни функции

За да използвате асинхронни функции, първо трябва да знаете две неща:

  1. Как да конвертирате обратни повиквания в обещания (прочетете по-горе)
  2. Как да използвате асинхронни функции (прочетете това, ако се нуждаете от помощ).

С асинхронни функции можете да напишете makeBurger, сякаш отново е синхронен!

const makeBurger = async () => {
  const beef = изчакайте getBeef ();
  const CookBeef = изчаквайте CookBeef (говеждо);
  const buns = изчакайте getBuns ();
  const burger = изчакайте putBeefBetweenBuns (сготвенBeef, кифли);
  връщане бургер;
};
// Направете и сервирайте бургер
makeBurger () след това (служи).

Имаме едно подобрение, което можем да направим в MakeBurger тук. Вероятно можете да получите двама помощници за getBuns и getBeef едновременно. Това означава, че можете да ги очаквате както с Promise.all.

const makeBurger = async () => {
  const [говеждо месо, кифлички] = очаквайте Promise.all (getBeef, getBuns);
  const CookBeef = изчаквайте CookBeef (говеждо);
  const burger = изчакайте putBeefBetweenBuns (сготвенBeef, кифлички);
  връщане бургер;
};
// Направете и сервирайте бургер
makeBurger () след това (служи).

(Забележка: Можете да направите същото с Promises ... но синтаксисът не е толкова хубав и ясен като функциите на async / чакане).

Обобщавайки

Обратното извикване не е толкова адско, колкото си мислите. Има четири лесни начина за управление на ада обратно:

  1. Пишете коментари
  2. Разделяне на функции на по-малки функции
  3. Използване на обещания
  4. Използване на Async / чакайте

Тази статия първоначално беше публикувана в моя блог.
Регистрирайте се за моя бюлетин, ако искате още статии, които да ви помогнат да станете по-добър разработчик на фронтенд.