Sådan håndteres indlejrede tilbagekald og undgår "tilbagekaldshelvede"

Foto af Jefferson Santos på Unsplash

JavaScript er et underligt sprog. Indimellem skal du håndtere et tilbagekald, der findes i et andet tilbagekald, der er i endnu et tilbagekald.

Folk kalder kærligt dette mønster callback helvede.

Det ser kinda sådan ud:

firstFunction (args, function () {
  secondFunction (args, function () {
    tredjeFunktion (args, funktion () {
      // Og så videre…
    });
  });
});

Dette er JavaScript til dig. Det er sjovt at se indlejrede tilbagekald, men jeg tror ikke, det er et "helvede". "Helvede" kan være håndterbar, hvis du ved, hvad du skal gøre med det.

Ved tilbagekald

Jeg antager, at du ved, hvad der er tilbagekald, hvis du læser denne artikel. Hvis du ikke gør det, skal du læse denne artikel for at få en introduktion til tilbagekald, før du fortsætter. Der snakker vi om, hvad tilbagekald er, og hvorfor du bruger dem i JavaScript.

Løsninger til callback helvede

Der er fire løsninger til callback helvede:

  1. Skriv kommentarer
  2. Opdel funktioner i mindre funktioner
  3. Brug af løfter
  4. Brug af Async / afvent

Før vi dykker ned i løsningen, lad os konstruere et callback helvede sammen. Hvorfor? Fordi det er for abstrakt til at se firstFunction, secondFunction og ThirdFunction. Vi ønsker at gøre det konkret.

Konstruktion af et helvede tilbagekald

Lad os forestille os, at vi prøver at lave en burger. For at lave en burger er vi nødt til at gennemgå følgende trin:

  1. Få ingredienser (vi antager, at det er en oksekødburger)
  2. Kog oksekødet
  3. Få burgerboller
  4. Læg det kogte oksekød mellem bollerne
  5. Server burgeren

Hvis disse trin er synkrone, ser du på en funktion, der ligner denne:

const makeBurger = () => {
  const beef = getBeef ();
  const patty = cookBeef (oksekød);
  const buns = getBuns ();
  const burger = putBeefBetweenBuns (boller, oksekød);
  returburger;
};
const burger = makeBurger ();
tjene (burger);

Lad os sige, i vores scenarie, at vi ikke selv kan lave burgeren. Vi er nødt til at instruere en hjælper om trinene til at lave burgeren. Når vi har instrueret hjælperen, skal vi vente på, at hjælperen er færdig, inden vi begynder på det næste trin.

Hvis vi vil vente på noget i JavaScript, skal vi bruge et tilbagekald. For at lave burgeren, skal vi først få oksekødet. Vi kan kun tilberede oksekødet, når vi får oksekødet.

const makeBurger = () => {
  getBeef (funktion (oksekød) {
    // Vi kan kun lave oksekød, når vi har fået det.
  });
};

For at tilberede oksekødet er vi nødt til at overføre oksekødet til cookBeef-funktionen. Ellers er der intet at lave mad! Derefter må vi vente på, at oksekødet bliver kogt.

Når oksekødet er kogt, får vi boller.

const makeBurger = () => {
  getBeef (funktion (oksekød) {
    cookBeef (oksekød, funktion (cookedBeef) {
      getBuns (funktion (boller) {
        // Læg patty i bolle
      });
    });
  });
};

Når vi har fået boller, er vi nødt til at lægge patty mellem boller. Det er her en burger bliver dannet.

const makeBurger = () => {
  getBeef (funktion (oksekød) {
    cookBeef (oksekød, funktion (cookedBeef) {
      getBuns (funktion (boller) {
        putBeefBetweenBuns (boller, oksekød, funktion (burger) {
            // Server burgeren
        });
      });
    });
  });
};

Endelig kan vi servere burgeren! Men vi kan ikke returnere burger fra makeBurger, fordi den er asynkron. Vi er nødt til at acceptere et tilbagekald for at servere burgeren.

const makeBurger = nextStep => {
  getBeef (funktion (oksekød) {
    cookBeef (oksekød, funktion (cookedBeef) {
      getBuns (funktion (boller) {
        putBeefBetweenBuns (boller, oksekød, funktion (burger) {
          NeXTStep (burger)
        })
      })
    })
  })
}
// Lav og server burgeren
makeBurger (funktion (burger) => {
  tjene (burger)
})

(Jeg havde det sjovt med at gøre dette callback helvedeeksempel ).

Første løsning på helvede til tilbagekald: Skriv kommentarer

MakeBurger-tilbagekaldshelvede er let at forstå. Vi kan læse det. Det bare ... ser ikke pænt ud.

Hvis du læser makeBurger for første gang, kan du tænke "Hvorfor fanden har vi brug for så mange tilbagekald for at lave en burger? Det giver ikke mening! ”.

I et sådant tilfælde ønsker du at give kommentarer for at forklare din kode.

// Opretter en burger
// makeBurger indeholder fire trin:
// 1. Hent oksekød
// 2. Kog oksekødet
// 3. Hent boller til burgeren
// 4. Læg det kogte oksekød mellem bollerne
// 5. Server burgeren (fra tilbagekaldet)
// Vi bruger tilbagekald her, fordi hvert trin er asynkron.
// Vi må vente på, at hjælperen afslutter det ene trin
// før vi kan starte næste trin
const makeBurger = nextStep => {
  getBeef (funktion (oksekød) {
    cookBeef (oksekød, funktion (cookedBeef) {
      getBuns (funktion (boller) {
        putBeefBetweenBuns (boller, oksekød, funktion (burger) {
          NeXTStep (burger);
        });
      });
    });
  });
};

I stedet for at tænke "wtf ?!", når du ser tilbagekaldet helvede, får du en forståelse af, hvorfor det skal skrives på denne måde.

Anden løsning på helvete til tilbagekald: Opdel tilbagekaldene i forskellige funktioner

Vores eksempel på callback helvede er allerede et eksempel på dette. Lad mig vise dig den trinvise imperativkode, og du kan se hvorfor.

For getBeef, vores første tilbagekald, er vi nødt til at gå til køleskabet for at få oksekødet. Der er to køleskabe i køkkenet. Vi er nødt til at gå til det rigtige køleskab.

const getBeef = nextStep => {
  const køleskab = venstre højre;
  const beef = getBeefFromFridge (køleskab);
  NeXTStep (oksekød);
};

For at tilberede oksekød skal vi sætte oksekødet i en ovn; drej ovnen til 200 grader, og vent i tyve minutter.

const cookBeef = (oksekød, nextStep) => {
  const workInProgress = putBeefinOven (oksekød);
  setTimeout (funktion () {
    NeXTStep (workInProgress);
  }, 1000 * 60 * 20);
};

Forestil dig nu, hvis du skal skrive hvert af disse trin i makeBurger ... vil du sandsynligvis besvime af den store mængde kode!

For et konkret eksempel på opdeling af tilbagekald i mindre funktioner kan du læse dette lille afsnit i min tilbagekaldsartikel.

Tredje løsning til callback helvede: Brug løfter

Jeg vil antage, at du ved, hvad løfter er. Hvis du ikke gør det, skal du læse denne artikel.

Løfter kan gøre callback helvede meget lettere at administrere. I stedet for den indlejrede kode, du ser ovenfor, har du denne:

const makeBurger = () => {
  return getBeef ()
    .then (oksekød => cookBeef (oksekød))
    .then (cookBeef => getBuns (oksekød))
    .then (bunsAndBeef => putBeefBetweenBuns (bunsAndBeef));
};
// Lav og server burger
makeBurger (). derefter (burger => server (burger));

Hvis du drager fordel af stilen med et enkelt argument med løfter, kan du justere ovenstående til dette:

const makeBurger = () => {
  return getBeef ()
    Ringing (cookBeef)
    .Derefter (getBuns)
    Ringing (putBeefBetweenBuns);
};
// Lav og server burger
makeBurger () og derefter (tjene).;

Meget lettere at læse og styre.

Men spørgsmålet er, hvordan konverterer du callback-baseret kode til løftebaseret kode.

Konvertering af tilbagekald til løfter

For at konvertere tilbagekald til løfter, er vi nødt til at oprette et nyt løfte til hver tilbagekald. Vi kan løse løftet, når tilbagekaldet er vellykket. Eller vi kan afvise løftet, hvis tilbagekaldet mislykkes.

const getBeefPromise = _ => {
  const køleskab = venstre højre;
  const beef = getBeefFromFridge (køleskab);
  returner nyt løfte ((løse, afvis) => {
    hvis (oksekød) {
      løse (oksekød);
    } andet {
      afvis (ny fejl (“Ikke mere oksekød!”));
    }
  });
};
const cookBeefPromise = oksekød => {
  const workInProgress = putBeefinOven (oksekød);
  returner nyt løfte ((løse, afvis) => {
    setTimeout (funktion () {
      vilje (workInProgress);
    }, 1000 * 60 * 20);
  });
};

I praksis vil tilbagekald muligvis allerede være skrevet til dig. Hvis du bruger Node, vil hver funktion, der indeholder et tilbagekald, have den samme syntaks:

  1. Tilbagekaldet ville være det sidste argument
  2. Tilbagekaldet har altid to argumenter. Og disse argumenter er i samme rækkefølge. (Fejl først, efterfulgt af hvad du er interesseret i).
// Den funktion, der er defineret for dig
const functionName = (arg1, arg2, callback) => {
  // Gør ting her
  tilbagekald (fejl, stuff);
};
// Hvordan du bruger funktionen
functionName (arg1, arg2, (err, stuff) => {
  hvis (fejle) {
  console.error (err);
  }
  // Gør ting
});

Hvis din tilbagekald har den samme syntaks, kan du bruge biblioteker som ES6 Promisify eller Denodeify (de-node-ify), der tilbagekaldelse til et løfte. Hvis du bruger Node v8.0 og nyere, kan du bruge util.promisify.

Alle tre af dem fungerer. Du kan vælge ethvert bibliotek, du vil arbejde med. Der er dog små nuancer mellem hver metode. Jeg overlader dig til at tjekke deres dokumentation for, hvordan du gør det.

Fjerde løsning til callback helvede: Brug asynkrone funktioner

For at bruge asynkrone funktioner skal du først kende to ting:

  1. Sådan konverteres tilbagekald til lovnader (læst ovenfor)
  2. Sådan bruges asynkrone funktioner (læs dette, hvis du har brug for hjælp).

Med asynkrone funktioner kan du skrive makeBurger, som om den er synkron igen!

const makeBurger = async () => {
  const beef = afvente getBeef ();
  const cookedBeef = afvente cookBeef (oksekød);
  const buns = afvente getBuns ();
  const burger = afvente putBeefBetweenBuns (cookBeef, boller);
  returburger;
};
// Lav og server burger
makeBurger () og derefter (tjene).;

Der er en forbedring, vi kan gøre for at gøre burgeren her. Du kan sandsynligvis få to hjælpere til at få Buns og get Beef på samme tid. Dette betyder, at du kan afvente dem begge med Promise.all.

const makeBurger = async () => {
  const [oksekød, boller] = afventer Promise.all (getBeef, getBuns);
  const cookedBeef = afvente cookBeef (oksekød);
  const burger = afvente putBeefBetweenBuns (cookBeef, boller);
  returburger;
};
// Lav og server burger
makeBurger () og derefter (tjene).;

(Bemærk: Du kan gøre det samme med løfter ... men syntaks er ikke så pæn og så klar som async / vente-funktioner).

Afslutter

Callback helvede er ikke så helvede som du tror. Der er fire nemme måder at administrere tilbagekaldshelvede på:

  1. Skriv kommentarer
  2. Opdel funktioner i mindre funktioner
  3. Brug af løfter
  4. Brug af Async / afvent

Denne artikel blev oprindeligt sendt på min blog.
Tilmeld mig mit nyhedsbrev, hvis du vil have flere artikler, der kan hjælpe dig med at blive en bedre frontend-udvikler.