9
votes

Comment gérer plusieurs attentes dans la fonction asynchrone

J'ai plusieurs appels d'API à effectuer qui récupèrent via une API, écrivent des données dans DB via API, envoient une sortie au front-end via une autre API.

J'ai écrit une fonction asynchrone avec await comme ci-dessous -

Les 2 premiers doivent s'exécuter l'un après l'autre mais le 3ème peut s'exécuter indépendamment et il n'est pas nécessaire d'attendre que les 2 premières instructions de récupération se terminent.

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();

Quelle est la meilleure façon de gérer ces multiples instructions d'extraction?


6 commentaires

Vous pouvez jeter un œil à Promise.all .


@YannickK Est-ce que Promise.all serait nécessaire ici? Ne pourrait-il pas simplement utiliser .then () à la place? Il n'attend pas l'achèvement des deux, mais plutôt le premier puis le deuxième, puis le troisième indépendamment de ces deux.


@Kobe Je pense que le principal problème dans ce cas est qu'OP veut séparer les appels serveur et client car ils ne dépendent pas l' un de l'autre - et ce serait ridicule en termes de performances s'ils s'attendaient l'un à l'autre - mais si l' un des eux échouent, vous voulez un rejet. Vous avez certainement raison de dire qu'il pourrait se passer de Promise.all , mais dans ce cas, j'imagine que ce serait plus propre (et plus facile à développer à l'avenir) s'il enveloppait tout dans un Promise.all appel Promise.all , spécifiquement pour erreur manipulation.


@Kobe Parce que Promise.all est essentiel pour une gestion correcte des erreurs et pour attendre la fin des promesses première-puis-deuxième et troisième.


La réponse la plus simple résout le mieux le problème, mais elle a malheureusement été rejetée sans raison. Cela vaut la peine d'essayer, @Yasar Abdulllah.


@YasarAbdullah Vous pouvez accepter une réponse (si cela vous aide) en cliquant sur le gros bouton à cocher gris sur son côté gauche. Si vous le souhaitez, vous pouvez ajouter +10 points à n'importe quel auteur de toute bonne réponse en cliquant sur le triangle gris supérieur


4 Réponses :


-2
votes

Exécuter une requête indépendante ( writeToDB ) au début et sans await

let writeToDB = fetch(url_for_writeToDB);

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

// ...


6 commentaires

@Bergi la question n'est pas sur la gestion des erreurs - la gestion des erreurs (comme try-catch) est importante mais c'est un sujet distinct


await a intégré la gestion des erreurs - lorsque la promesse est rejetée, vous obtenez une exception, la promesse renvoyée par l'appel de async function sera rejetée et l'appelant le remarquera. Si vous suggérez de supprimer le mot-clé await et d'exécuter la chaîne de promesse indépendamment, vous ne devez pas le faire sans penser à la gestion des erreurs. Une réponse qui n'en fait pas au moins mention (ou qui préserve le comportement d'origine de l'appelant qui est rejeté) est une mauvaise réponse.


Cette réponse résout le problème d'OP et c'est aussi la plus simple de toutes les réponses présentées jusqu'à présent. Je suis curieux de savoir pourquoi cela a été déclassé? L'OP a déclaré que " le 3ème peut fonctionner indépendamment et il n'est pas nécessaire d'attendre que les 2 premières instructions de récupération soient terminées. ", Donc l'exécuter en premier est très bien.


@AndreasPizsa C'est trop simple . Je ne l'aurais pas rejeté s'il y avait au moins une clause de non-responsabilité indiquant que les erreurs ne sont pas gérées - dès que cela est mentionné, n'importe qui peut se faire sa propre opinion sur l'opportunité ou non de son cas - cela pourrait même l'être pour le PO . Mais ignorer cela conduit aux pires bugs de tous et rend cette réponse inutile.


Merci d'avoir commenté @Bergi. Étant donné que la question d'OP n'incluait pas non plus la gestion des erreurs, je suppose qu'elle est laissée de côté pour des raisons de brièveté et au-delà de la portée de la question. Mais oui, n'importe qui peut voter avec sa propre opinion.


@AndreasPizsa Comme je l' ai dit, await - t erreurs promesse de poignée en lançant une exception, ce qui pourrait se faire prendre quelque part (dans le code non représenté). Mais cette réponse modifie le comportement de gestion des erreurs du code sans le mentionner, et c'est dangereux.



1
votes

Vous pouvez utiliser .then() , plutôt qu'attendre:

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  
  })


1 commentaires

N'oubliez pas de gérer les erreurs sur ces chaînes de promesses - ou Promise.all les et renvoyez-les à l'appelant.



1
votes

C'est plus facile si vous travaillez avec des "créateurs" de promesses (= fonction qui renvoient des promesses) plutôt qu'avec des promesses brutes. Tout d'abord, définissez:

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

qui renvoie un tel "créateur". Maintenant, voici deux utilitaires pour le chaînage série et parallèle, qui acceptent à la fois les promesses brutes et les "créateurs":

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()

Ensuite, le code principal peut être écrit comme ceci:

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

Si vous n'aimez pas le wrapper dédié "creator", vous pouvez définir fetchJson normalement

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

et utilisez des continuations en ligne là où les series ou parallel sont appelés:

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

Pour pousser l'idée plus loin, on peut faire des series et parallel "créateurs" de retour parallel aussi bien que des promesses. De cette façon, nous pouvons construire des "circuits" imbriqués arbitraires de promesses série et parallèle et obtenir les résultats dans l'ordre. Exemple de travail complet:

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;
}
const fetchJson = (url, opts) => () => fetch(url, opts).then(r => r.json())


8 commentaires

pourquoi ne pas simplement retourner la promesse de fetchJson ? Je ne vois aucun avantage à emballer une promesse avec une autre fonction. Et l'utilité de la series est discutable car la fonction suivante d'une série nécessite généralement la valeur de retour de la ou des fonctions précédentes.


@marzelin: les promesses commencent immédiatement à la création, donc les series(promiseA, promiseB) commenceront A et B en même temps.


Ah oui. Toute cette cérémonie juste pour utiliser des series . Je préférerais async iife pour les séquences et Promise.allSettled au lieu de parallel .


@marzelin: Je trouve ce nettoyeur de syntaxe (voir mise à jour).


@georg vos solutions semblent compliquées - pouvez-vous nous expliquer quels sont les avantages de votre approche?


@ KamilKiełczewski: il existe deux types de réponses StackOverflow: a) une combinaison plus ou moins intelligente de composants intégrés qui résout le problème spécifique de l'OP ou b) des techniques génériques qui montrent également comment résoudre une classe plus large de problèmes similaires. Personnellement, je préfère ce dernier, car c'est ainsi que vous écrivez de vrais programmes.


@georg - ok, mais pouvez-vous répondre à ma question - quels sont les avantages de vos techniques génériques utilisées dans cette réponse? Habituellement, un code plus générique a besoin d'une documentation sur l'idée cachée derrière et les avantages / fonctionnalités qu'elle fournit (par exemple, comme les frameworks).


@ KamilKiełczewski: J'ai mis à jour l'article ... L'idée est que cette syntaxe de continuation permet de créer des "flux" de promesses imbriquées sans trop de tracas.



2
votes

Il existe de nombreuses façons, mais la plus universelle consiste à envelopper chaque chemin de code asynchrone dans une fonction asynchrone. Cela vous donne la flexibilité de mélanger et de faire correspondre les valeurs de retour asynchrones à votre guise. Dans votre exemple, vous pouvez même coder en ligne avec async iife:

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();
  })()
]);


1 commentaires

Enfin, une bonne réponse! :RÉ