Sök…


Introduktion

Node är ett programmeringsspråk där allt kan köras på ett asynkront sätt. Nedan kan du hitta några exempel och de typiska sakerna med asynkront arbete.

Syntax

  • doSomething ([args], funktion ([argsCB]) {/ * gör något när du är klar * /});
  • doSomething ([args], ([argsCB]) => {/ * gör något när du är klar * /});

Återuppringningsfunktioner

Återuppringningsfunktioner i JavaScript

Återuppringningsfunktioner är vanliga i JavaScript. Återuppringningsfunktioner är möjliga i JavaScript eftersom funktioner är förstklassiga medborgare .

Synkrona återuppringningar.

Återuppringningsfunktioner kan vara synkrona eller asynkrona. Eftersom asynkrona återuppringningsfunktioner kan vara mer komplexa är här ett enkelt exempel på en synkron återuppringningsfunktion.

// a function that uses a callback named `cb` as a parameter
function getSyncMessage(cb) {
    cb("Hello World!");
}

console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getSyncMessage(function(message) {
    console.log(message);
});
console.log("After getSyncMessage call");

Utgången för ovanstående kod är:

> Before getSyncMessage call
> Hello World!
> After getSyncMessage call

Först kommer vi att gå igenom hur koden ovan körs. Detta är mer för de som inte redan förstår begreppet återuppringning om du redan förstår det gärna hoppa över det här stycket. Först analyseras koden och sedan är den första intressanta saken som händer rad 6 som utförs Before getSyncMessage call utgången Before getSyncMessage call till konsolen. Sedan exekveras rad 8 som kallar funktionen getSyncMessage skickar in en anonym funktion som ett argument för parametern namnet cb i getSyncMessage funktionen. Exekvering görs nu inuti getSyncMessage funktionen på rad 3 som kör funktionen cb som just skickades in, detta samtal skickar en argumentsträng "Hello World" för det parametern message i den vidarebefordrade anonyma funktionen. Utförandet går sedan till rad 9 som loggar Hello World! till konsolen. Sedan genomgår exekveringen processen att lämna samtalstacket ( se även ) träffa linje 10 sedan linje 4 och sedan slutligen tillbaka till linje 11.

Lite information att veta om återuppringningar i allmänhet:

  • Den funktion du skickar in till en funktion som återuppringning kan kallas noll gånger, en gång eller flera gånger. Allt beror på genomförandet.
  • Återuppringningsfunktionen kan kallas synkront eller asynkront och eventuellt både synkront och asynkront.
  • Precis som normala funktioner är namnen du ger parametrar för din funktion inte viktiga men ordningen är. Så till exempel på linje 8 message kunde ha fått sitt namn statement , msg , eller om du är meningslösa något liknande jellybean . Så du bör veta vilka parametrar som skickas till din återuppringning så att du kan få dem i rätt ordning med egna namn.

Asynkrona återuppringningar.

En sak att notera om JavaScript är att det är synkront som standard, men det finns API: er som ges i miljön (webbläsare, Node.js, etc.) som kan göra det asynkron (det finns mer om det här ).

Några vanliga saker som är asynkrona i JavaScript-miljöer som accepterar återuppringningar:

  • evenemang
  • setTimeout
  • setInterval
  • hämta API
  • Löften

Även varje funktion som använder en av ovanstående funktioner kan lindas med en funktion som tar en återuppringning och återuppringningen skulle då vara en asynkron återuppringning (även om inslagning av löften med en funktion som tar ett återuppringning troligtvis kan betraktas som ett antimönster som det finns mer föredragna sätt att hantera löften).

Så med tanke på den informationen kan vi konstruera en asynkron funktion liknande den ovanstående synkrona.

// a function that uses a callback named `cb` as a parameter
function getAsyncMessage(cb) {
    setTimeout(function () { cb("Hello World!") }, 1000);
}

console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getAsyncMessage(function(message) {
    console.log(message);
});
console.log("After getSyncMessage call");

Vilket skriver ut följande på konsolen:

> Before getSyncMessage call
> After getSyncMessage call
// pauses for 1000 ms with no output
> Hello World!

Radutförandet går till rad 6-loggar "Innan getSyncMessage-samtal". Sedan kör cb till rad 8 som ringer getAsyncMessage med en återuppringning för parametern. Rad 3 exekveras sedan vilket kallar setTimeout med ett återuppringning som det första argumentet och numret 300 som det andra argumentet. setTimeout gör vad den gör och håller fast vid den återuppringningen så att den kan ringa den senare på 1000 millisekunder, men efter att ha ställt in timeout och innan den pausar 1000 millisekunder lämnar den exekveringen tillbaka till där den slutade så att den går till rad 4 , därefter linjen 11, och gör sedan en paus för en sekund och setTimeout anropar sedan dess återanropsfunktion som tar exekverings tillbaka till ledningen 3, där getAsyncMessages återuppringning anropas med värdet "Hello World" för dess message som sedan loggas till konsolen på ledningen 9 .

Återuppringningsfunktioner i Node.js

NodeJS har asynkrona återuppringningar och levererar vanligtvis två parametrar till dina funktioner som ibland konventionellt kallas err och data . Ett exempel med att läsa en filtext.

const fs = require("fs");

fs.readFile("./test.txt", "utf8", function(err, data) {
    if(err) {
        // handle the error 
    } else {
        // process the file text given with data
    }
});

Detta är ett exempel på en återuppringning som kallas en enda gång.

Det är god praxis att hantera felet på något sätt även om du bara loggar in det eller kastar det. Det andra är inte nödvändigt om du kastar eller återvänder och kan tas bort för att minska indragningen så länge du stoppar exekveringen av den aktuella funktionen i if genom att göra något som att kasta eller återvända.

Även om det kan vara vanligt att se err , data kan det inte alltid vara så att dina återanrop kommer att använda detta mönster är det bäst att titta på dokumentation.

Ett annat exempel återuppringning kommer från expressbiblioteket (express 4.x):

// this code snippet was on http://expressjs.com/en/4x/api.html
const express = require('express');
const app = express();

// this app.get method takes a url route to watch for and a callback
// to call whenever that route is requested by a user.
app.get('/', function(req, res){
  res.send('hello world');
});

app.listen(3000);

Detta exempel visar en återuppringning som kallas flera gånger. Återuppringningen är försedd med två objekt som paramer som heter här som req och res Dessa namn motsvarar respektive begäran och svar, och de ger sätt att se förfrågan som kommer in och ställa in svaret som kommer att skickas till användaren.

Som du kan se finns det olika sätt en återuppringning kan användas för att köra synkronisering och asynkod i JavaScript och återuppringningar är mycket allestädes närvarande i JavaScript.

Kodsexempel

Fråga: Vad är resultatet av koden nedan och varför?

setTimeout(function() {
    console.log("A");
}, 1000);

setTimeout(function() {
    console.log("B");
}, 0);

getDataFromDatabase(function(err, data) {
    console.log("C");
    setTimeout(function() {
        console.log("D");
    }, 1000);
});

console.log("E");

Output: Detta är känt med säkerhet: EBAD . C är okänt när det kommer att loggas.

Förklaring: Kompilatorn stannar inte på setTimeout och getDataFromDatabase . Så den första raden han loggar är E Återuppringningsfunktionerna (första argumentet med setTimeout ) kommer att köras efter den inställda timeouten på ett asynkront sätt!

Fler detaljer:

  1. E har ingen setTimeout
  2. B har en inställd tidsgräns på 0 millisekunder
  3. A har en inställd tidsgräns på 1000 millisekunder
  4. D måste begära en databas, efter den måste D vänta 1000 millisekunder så att den kommer efter A
  5. C är okänt eftersom det är okänt när databasens data begärs. Det kan vara före eller efter A

Async-felhantering

Försök fånga

Fel måste alltid hanteras. Om du använder synkron programmering kan du använda en try catch . Men detta fungerar inte om du arbetar asynkron! Exempel:

try {
    setTimeout(function() {
        throw new Error("I'm an uncaught error and will stop the server!");
    }, 100); 
}
catch (ex) {
    console.error("This error will not be work in an asynchronous situation: " + ex);
}

Async-fel hanteras endast i återuppringningsfunktionen!

Arbetsmöjligheter

v0.8

Eventhanterare

De första versionerna av Node.JS fick en händelsehanterare.

process.on("UncaughtException", function(err, data) { 
    if (err) {
        // error handling
    }
});
v0.8

domäner

Inuti en domän släpps felen via händelsemitterarna. Genom att använda detta är alla fel, tidtagare, återuppringningsmetoder implicit endast registrerade inom domänen. Vid ett fel, skicka en felhändelse och krasch inte applikationen.

var domain = require("domain");
var d1 = domain.create();
var d2 = domain.create();

d1.run(function() {
    d2.add(setTimeout(function() {
        throw new Error("error on the timer of domain 2");
    }, 0));
});

d1.on("error", function(err) {
    console.log("error at domain 1: " + err);
});

d2.on("error", function(err) {
    console.log("error at domain 2: " + err);
});

Hellback helvetet

Callback helvete (även en pyramid of doom eller boomerang effekt) uppstår när du häckar för många återuppringningsfunktioner i en återuppringningsfunktion. Här är ett exempel för att läsa en fil (i ES6).

const fs = require('fs');
let filename = `${__dirname}/myfile.txt`;

fs.exists(filename, exists => {
    if (exists) {
        fs.stat(filename, (err, stats) => {
            if (err) {
                throw err;
            }
            if (stats.isFile()) {
                fs.readFile(filename, null, (err, data) => {
                    if (err) {
                        throw err;
                    }
                    console.log(data);
                });
            }
            else {
                throw new Error("This location contains not a file");
            }
        });
    }
    else {
        throw new Error("404: file not found");
    }
}); 

Hur man undviker "Callback Hell"

Det rekommenderas att inte bo mer än två återuppringningsfunktioner. Detta hjälper dig att upprätthålla kodläsbarheten och kommer att bli mycket lättare att underhålla i framtiden. Om du har behov av att häcka mer än två återuppringningar kan du försöka använda distribuerade händelser istället.

Det finns också ett bibliotek som heter async som hjälper till att hantera återuppringningar och deras utförande tillgängligt kl. Det ökar läsbarheten för återuppringningskod och ger dig mer kontroll över ditt återuppringningskodflöde, inklusive att du kan köra dem parallellt eller i serie.

Infödda löften

v6.0.0

Löften är ett verktyg för asynk programmering. I JavaScript är löften kända för sina then metoder. Löftena har två huvudstater som ”avvaktar” och ”avvecklas”. När ett löfte är "avgjort" kan det inte gå tillbaka till "väntande". Detta innebär att löften oftast är bra för händelser som bara inträffar en gång. Den "avvecklade" staten har två stater också "löst" och "avvisats". Du kan skapa ett nytt löfte med det new nyckelordet och skicka en funktion till konstruktörens new Promise(function (resolve, reject) {}) .

Funktionen som överförs till Promise-konstruktören får alltid en första och en andra parameter som vanligtvis kallas resolve och reject resp. Namnet på dessa två parametrar är konvention, men de kommer att sätta löften i antingen det "lösta" tillståndet eller det "avvisade" tillståndet. När endera av dessa kallas löftet går från att vara "väntande" till "avgjort". resolve kallas när den önskade åtgärden, som ofta är asynkron, har utförts och reject används om åtgärden har felat.

I nedanstående timeout är en funktion som returnerar ett löfte.

function timeout (ms) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve("It was resolved!");
    }, ms)
  });
}

timeout(1000).then(function (dataFromPromise) {
  // logs "It was resolved!"
  console.log(dataFromPromise);
})

console.log("waiting...");

konsolutgång

waiting...
// << pauses for one second>>
It was resolved!

När timeout kallas funktionen som skickas till Promise-konstruktorn utförs utan dröjsmål. Sedan exekveras setTimeout-metoden och dess återuppringning är inställd på avfyrning under nästa ms millisekunder, i detta fall ms=1000 . Eftersom återuppringningen till setTimeout inte är avstängd, återger timeoutfunktionen kontrollen till samtalsomfånget. Kedjan av then metoder lagras sedan för att kallas senare när / om löfte har lösts. Om det fanns catch här skulle de också lagras, men skulle avfyras när / om löfte "avvisar".

Skriptet skriver sedan ut "väntar ...". En sekund senare kallar setTimeout sin återuppringning som kallar lösningsfunktionen med strängen "Det var löst!". Den strängen överförs sedan till den then metodens återuppringning och loggas sedan till användaren.

På samma sätt kan du linda in den asynkrona setTimeout-funktionen som kräver återuppringning. Du kan linda in alla singulära asynkrona åtgärder med ett löfte.

Läs mer om löften i JavaScript-dokumentationen Löften .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow