Umgang mit mehreren Wartezeiten in der asynchronen Funktion

8

Ich muss mehrere API-Aufrufe durchführen, die über eine API abgerufen, Daten über die API in die Datenbank geschrieben und die Ausgabe über eine andere API an das Front-End gesendet werden.

Ich habe eine asynchrone Funktion mit Warten wie unten geschrieben -

Die ersten beiden sollten nacheinander ausgeführt werden, die dritte kann jedoch unabhängig voneinander ausgeführt werden, ohne dass Sie warten müssen, bis die ersten beiden Abrufanweisungen abgeschlossen sind.

let getToken= await fetch(url_for_getToken);
let getTokenData = await getToken.json();

let writeToDB = await fetch(url_for_writeToDB);
let writeToDBData = await writeToDB.json();

let frontEnd = await fetch(url_for_frontEnd);
let frontEndData = await frontEnd.json();

Was ist der beste Weg, um mit solchen Mehrfachabrufanweisungen umzugehen?

Yasar Abdullah
quelle
6
Sie könnten sich Promise.all ansehen .
Yannick K
2
@YannickK Wäre Promise.all hier notwendig? Könnte er nicht stattdessen einfach .then () verwenden? Er wartet nicht auf die Fertigstellung von beiden, sondern auf die erste, dann die zweite, dann die dritte, unabhängig von diesen beiden.
Kobe
1
@Kobe ich in diesem Fall das Hauptproblem denke , dass OP will , ist die Server- und Client - Anrufe trennen , da sie nicht sind abhängig voneinander - und es wäre dumm leistungs klug, wenn sie aufeinander gewartet - aber wenn jeder von Sie scheitern, wenn Sie eine Ablehnung wünschen. Sie haben definitiv Recht, dass er darauf verzichten könnte Promise.all, aber in diesem Fall würde ich mir vorstellen, dass es sauberer (und in Zukunft einfacher zu bauen) wäre, wenn er alles in einem Promise.allAufruf zusammenfassen würde, speziell für die Fehlerbehandlung.
Yannick K
@Kobe Da dies Promise.allfür die ordnungsgemäße Fehlerbehandlung und das Warten auf die Erfüllung des ersten, zweiten und dritten Versprechens unerlässlich ist .
Bergi
1
Die einfachste Antwort löst das Problem am besten, wurde aber leider zu Unrecht abgelehnt. Es lohnt sich, es zu versuchen, @Yasar Abdulllah.
AndreasPizsa

Antworten:

2

Es gibt viele Möglichkeiten, aber die universellste besteht darin, jeden asynchronen Codepfad in eine asynchrone Funktion zu verpacken. Dies gibt Ihnen die Flexibilität, asynchrone Rückgabewerte nach Belieben zu mischen und abzugleichen. In Ihrem Beispiel können Sie sogar Code mit asynchronem iife einbinden:

await Promise.all([
  (async() => {
    let getToken = await fetch(url_for_getToken);
    let getTokenData = await getToken.json();

    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
  })(),
  (async() => {
    let frontEnd = await fetch(url_for_frontEnd);
    let frontEndData = await frontEnd.json();
  })()
]);
Marzelin
quelle
Endlich eine richtige Antwort! : D
Bergi
1

Sie können verwenden .then(), anstatt zu warten:

fetch(url_for_getToken)
  .then(getToken => getToken.json())
  .then(async getTokenData => {
    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
    // Carry on
  })

fetch(url_for_frontEnd)
  .then(frontEnd => frontEnd.json())
  .then(frontEndData => {
    // Carry on  
  })
Kobe
quelle
1
Vergessen Sie nicht, Fehler in diesen Versprechensketten zu behandeln - oder Promise.allsie und senden Sie sie an den Anrufer zurück.
Bergi
1

Es ist einfacher, mit vielversprechenden "Schöpfern" (= Funktion, die Versprechen zurückgibt) zu arbeiten, als mit rohen Versprechen. Definieren Sie zunächst:

const fetchJson = (url, opts) => () => fetch(url, opts).then(r => r.json())

was einen solchen "Schöpfer" zurückgibt. Hier sind zwei Dienstprogramme für die serielle und parallele Verkettung, die sowohl rohe Versprechen als auch "Schöpfer" akzeptieren:

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => Promise.all(fns.map(call));

async function series(...fns) {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
}

Dann kann der Hauptcode folgendermaßen geschrieben werden:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        fetchJson(url_for_getToken),
        fetchJson(url_for_writeToDB),
    ),
    fetchJson(url_for_frontEnd),
)

Wenn Ihnen der dedizierte "Creator" -Wrapper nicht gefällt, können Sie ihn fetchJsonnormal definieren

const fetchJson = (url, opts) => fetch(url, opts).then(r => r.json())

und verwenden Sie Inline-Fortsetzungen genau dort, wo seriesoder parallelaufgerufen werden:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        () => fetchJson('getToken'),
        () => fetchJson('writeToDB'),
    ),
    () => fetchJson('frontEnd'), // continuation not necessary, but looks nicer
)

Um die Idee weiter voranzutreiben, können wir auch "Schöpfer" machen seriesund parallelzurückgeben, anstatt Versprechen. Auf diese Weise können wir beliebig verschachtelte "Schaltkreise" aus seriellen und parallelen Versprechungen erstellen und die Ergebnisse in der richtigen Reihenfolge erhalten. Vollständiges Arbeitsbeispiel:

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => () => Promise.all(fns.map(call));

const series = (...fns) => async () => {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
};

//

const request = (x, time) => () => new Promise(resolve => {
    console.log('start', x);
    setTimeout(() => {
        console.log('end', x)
        resolve(x)
    }, time)
});

async function main() {
    let chain = series(
        parallel(
            series(
                request('A1', 500),
                request('A2', 200),
            ),
            series(
                request('B1', 900),
                request('B2', 400),
                request('B3', 400),
            ),
        ),
        parallel(
            request('C1', 800),
            series(
                request('C2', 100),
                request('C3', 100),
            )
        ),
    );

    let results = await chain();

    console.log(JSON.stringify(results))
}

main()
.as-console-wrapper { max-height: 100% !important; top: 0; }

georg
quelle
Warum gibst du nicht einfach das Versprechen zurück fetchJson? Ich sehe keine Vorteile darin, ein Versprechen mit einer anderen Funktion zu versehen. Die Nützlichkeit von seriesist fraglich, da die nächste Funktion in einer Reihe typischerweise den Rückgabewert der vorherigen Funktion (en) erfordert.
Marzelin
@marzelin: Versprechen beginnen sofort mit der Erstellung, series(promiseA, promiseB)beginnen also Aund Bgleichzeitig.
Georg
1
Ah ja. All diese Zeremonie nur zu nutzen series. Ich würde lieber async iife für Sequenzen und Promise.allSettledstattdessen bevorzugen parallel.
Marzelin
@marzelin: Ich finde diesen Syntax Cleaner (siehe Update).
Georg
1
@ KamilKiełczewski: Ich habe den Beitrag aktualisiert ... Die Idee ist, dass diese Fortsetzungssyntax das Erstellen verschachtelter Versprechen "Flows" ohne großen Aufwand ermöglicht.
Georg
-2

Führen Sie writeToDBzu Beginn und ohne unabhängige Anfrage ( ) ausawait

let writeToDB = fetch(url_for_writeToDB);

let getToken = await fetch(url_for_getToken);
let getTokenData = await getToken.json();

// ...

Kamil Kiełczewski
quelle
1
@Bergi die Frage ist nicht über die Fehlerbehandlung - Fehlerbehandlung (wie Try-Catch) ist wichtig, aber es ist ein separates Thema
Kamil Kiełczewski
awaitDie Fehlerbehandlung ist integriert. Wenn das Versprechen async functionabgelehnt wird , erhalten Sie eine Ausnahme. Das vom Anruf zurückgegebene Versprechen wird abgelehnt und der Anrufer wird es bemerken. Wenn Sie vorschlagen, das awaitSchlüsselwort zu löschen und die Versprechenskette unabhängig auszuführen, dürfen Sie dies nicht tun, ohne über die Fehlerbehandlung nachzudenken. Eine Antwort, die es zumindest nicht erwähnt (oder das ursprüngliche Verhalten des Anrufers beibehält, der eine Ablehnung erhält), ist eine schlechte Antwort.
Bergi
1
Diese Antwort löst das Problem von OP und ist auch die einfachste aller bisher vorgestellten Antworten. Ich bin gespannt, warum dies abgelehnt wurde. Das OP gab an, dass " die dritte unabhängig ausgeführt werden kann und nicht warten muss, bis die ersten beiden Abrufanweisungen abgeschlossen sind." ". Daher ist es in Ordnung, sie zuerst auszuführen.
AndreasPizsa
@AndreasPizsa Es ist zu einfach . Ich hätte es nicht abgelehnt, wenn es zumindest einen Haftungsausschluss gegeben hätte, dass Fehler nicht behandelt werden - sobald es erwähnt wird, kann jeder seine eigene Meinung darüber abgeben, ob es für seinen Fall angemessen ist oder nicht - es könnte sogar für das OP sein . Das Ignorieren führt jedoch zu den schlimmsten Fehlern von allen und macht diese Antwort nicht nützlich.
Bergi
1
Danke, dass du @Bergi kommentiert hast. Angesichts der Tatsache, dass die Frage von OP auch keine Fehlerbehandlung beinhaltete, würde ich davon ausgehen, dass sie der Kürze halber weggelassen wird und den Rahmen der Frage sprengt. Aber ja, jeder kann mit seiner eigenen Meinung abstimmen.
AndreasPizsa