2
votes

Promettre et modifier la valeur de retour

Je dois modifier le code existant pour prendre en charge les résultats synchrones et asynchrones. Bien que je puisse facilement gérer Task et wait en C #, même après avoir beaucoup lu sur MDN et d'autres pages, je ne peux tout simplement pas comprendre la promesse de JavaScript .

Le code existant ressemble à ceci:

function dispatchCall() {
    // ...

    try {
        let result = fn.apply(context, args);

        // --------------------------------------------------
        if (result && typeof result.then === "function") {
            result.then(function (result) {
                // Like so?
                if (result !== undefined) {
                    return { status: 0, result: result };
                }
                return { status: 0 };
            })
            .catch(function (err) {
                // Does this catch errors?
                if (typeof err === "object") {
                    return { status: 400, errorMessage: err.name + ", " + err.message, stack: err.stack };
                }
                return { status: 400, errorMessage: err };
            });
            return new Promise(function(resolve, reject) {
                // What about this?
                // When should I call resolve and reject and with what arguments?
            });
            // Must return a Promise and not continue at this point!
        }
        // --------------------------------------------------

        if (result !== undefined) {
            return { status: 0, result: result };
        }
        return { status: 0 };
    }
    catch (err) {
        if (typeof err === "object") {
            return { status: 400, errorMessage: err.name + ", " + err.message, stack: err.stack };
        }
        return { status: 400, errorMessage: err };
    }
}

fn est la fonction à appeler. Il est défini par l'utilisateur de mon API donc je ne sais pas ce qu'il va faire. Pour l'instant, il retournera toujours une valeur ou lèvera une exception. Cette valeur sera enveloppée dans un objet message qui est renvoyé à l'appelant distant de dispatchCall.

Maintenant, fn doit renvoyer un Promesse car elle doit être utilisée dans un workflow asynchrone où le résultat n'est pas immédiatement disponible.

Je dois tester si le résultat est une Promise (ou "thenable") et agissez en conséquence. Dans ce cas, lorsque la promesse de résultat est résolue, je dois envelopper la valeur de résultat dans l'objet de message approprié et la transmettre comme une autre promesse à l'appelant de dispatchCall . Je peux alors le gérer facilement.

C'est cette "transmission et modification de la valeur" que je ne peux pas résoudre.

Voici comment je commencerais les choses:

function dispatchCall() {
    // ...

    try {
        let result = fn.apply(context, args);

        if (result !== undefined) {
            return { status: 0, result: result };
        }
        return { status: 0 };
    }
    catch (err) {
        if (typeof err === "object") {
            return { status: 400, errorMessage: err.name + ", " + err.message, stack: err.stack };
        }
        return { status: 400, errorMessage: err };
    }
}

Comment cela devrait-il être collé ensemble?

Après avoir regardé les tableaux de support, j'ai décidé d'abandonner le support d'Internet Explorer et d'utiliser ES6 Promises. Aucune bibliothèque externe impliquée. Dans le cas où certains IE exécuteraient cela, il devrait continuer à fonctionner avec des fonctions synchrones et est autorisé à échouer misérablement avec du code asynchrone.

Mes environnements cibles sont les navigateurs et Node.js.

p >


3 commentaires

Avez-vous vraiment besoin de renvoyer cet objet de manière synchrone lorsque fn () n'a pas retourné de promesse? Ce serait beaucoup plus facile si votre fonction pouvait toujours renvoyer une promesse.


@Bergi Oui, car je ne veux pas utiliser Promise sauf si nécessaire.


@ygoe doit changer votre façon de penser. Si la possibilité de retourner une promesse dans une partie de celle-ci devrait retourner la promesse dans toutes les conditions et votre appel à dispatchCall () aura toujours un then () ou async / attendre


3 Réponses :


2
votes

Comme le résultat peut être récupéré de manière asynchrone, le consommateur de dispatchCall doit avoir une logique pour attendre que les données éventuellement asynchrones reviennent. Une option serait pour dispatchCall de renvoyer une promesse qui se résout finalement en objet {status, result} (ou {status, error} objet ) vous recherchez:

return Promise.resolve({ status: 0, result: result });

Et dans le consommateur de dispatchCall , vérifiez si la valeur renvoyée est une promesse - si c'est le cas, appelez . puis dessus:

const dispatchResult = dispatchCall();
if (typeof dispatchResult.then === 'function') {
  dispatchResult.then(({ status, result, error }) => {
    // do stuff with status, result, error here
    // if there was an error, result will be undefined
  });
} else {
  // do stuff with dispatchResult.status, .result, .errorMessage
}

Vous pouvez également envisager de renvoyer une promesse indépendamment du fait que résultat soit un Promettre ou non, afin de faciliter la gestion du code - par exemple, dans la section synchrone:

if (typeof result.then === "function") {
  return result
    .then((resolveValue) => {
      return { status: 0, result: resolveValue };
    })
    .catch((error) => {
      return { status: 400, error };
    })
}


0 commentaires

4
votes

Il n'est pas nécessaire de créer une promesse avec nouvelle promesse lorsque vous avez une table.

Vous pouvez simplement renvoyer return result.then (...... code>, ou si vous voulez être sûr de renvoyer une instance Promise (pas seulement ce que then (). catch () renvoie), transmettez le résultat à: Promise.resolve ( )

    if (typeof result.then === "function") {
        return Promise.resolve(result).then(function (result) {
            if (result !== undefined) {
                return { status: 0, result: result };
            }
            return { status: 0 };
        })
        .catch(function (err) {
            if (typeof err === "object") {
                return { status: 400, errorMessage: err.name + ", " + err.message, stack: err.stack };
            }
            return { status: 400, errorMessage: err };
        });
    }

Si une erreur se produit dans le rappel catch , la promesse retournée sera résolue comme rejetée. L'appelant peut gérer cela avec sa propre catch qui lui est enchaînée.


2 commentaires

Vous encapsuleriez uniquement le résultat dans l'appel Promise.resolve () , pas le résultat de l'appel de la méthode (et même le chaînage d'un catch ) sur la table.


OK, je comprends - un peu lent aujourd'hui :-) Merci pour l'indice.



0
votes

Avez-vous vraiment besoin de renvoyer cet objet de manière synchrone lorsque fn () n'a pas renvoyé de promesse? Ce serait beaucoup plus facile si votre fonction pouvait toujours renvoyer une promesse. Il vous suffirait de résoudre avec la valeur, et que ce soit une valeur simple, une valeur acceptable ou une promesse, vous en retireriez une promesse.

Ensuite, vous enchaîneriez votre logique de gestion des résultats / erreurs à cela, une seule fois:

function dispatchCall() {
    // ...

    // return Promise.resolve(fn.apply(context, args))… - doesn't catch synchronous exceptions
    return new Promise(function(resolve) {
        resolve(fn.apply(context, args)); // promise constructor catches exceptions
    }).then(function (result) {
        // the promise was fulfilled
        if (result !== undefined) {
            return { status: 0, result: result };
        } else {
            return { status: 0 };
        }
    }, function (err) {
        // the promise was rejected
        if (typeof err === "object") {
            return { status: 400, errorMessage: err.name + ", " + err.message, stack: err.stack };
        } else {
            return { status: 400, errorMessage: err };
        }
    });
}


6 commentaires

Oui, car je ne souhaite pas utiliser Promise sauf si nécessaire. De plus, très important, fn peut également ne rien renvoyer du tout, auquel cas je dois envoyer un message avec le statut 0 mais aucun résultat. Ceci est pour les fonctions vides.


@ygoe Si fn ne renvoie rien du tout, l'objet aura une propriété result d'une valeur indéfinie . La sérialisation JSON supprimera cela complètement. Je doute qu'il soit important d'omettre complètement la propriété.


@ygoe Qu'entendez-vous par «sauf si nécessaire»? Les promesses sont bon marché et leur utilisation rend tout beaucoup plus simple à gérer. Existe-t-il un besoin réel d'un résultat synchrone?


Je l'ai recherché et il n'y a aucun besoin réel d'un résultat synchrone de dispatchCall . Mais comme je l'ai dit, j'aimerais maintenir le support d'IE pour les scénarios réalisables. Si l'utilisateur de ma bibliothèque décide d'ajouter une bibliothèque tierce pour les promesses et que ma bibliothèque pourrait simplement la réutiliser, c'est bien, mais je ne veux pas introduire une telle dépendance à moins que cela ne soit nécessaire, c'est-à-dire que l'utilisateur veut du code asynchrone.


@ygoe Si c'est le choix, alors vous devriez probablement mieux livrer deux versions de votre bibliothèque, une qui est synchrone et une autre qui utilise des promesses. Avoir deux bases de code (ou peut-être simplement des options de construction) sera plus facile à gérer que de mélanger la logique de synchronisation et asynchrone dans le même code et d'avoir une dépendance facultative sur les promesses.


Oh, et la sérialisation JSON peut ignorer la valeur non définie (juste vérifiée ici dans Firefox), mais la sérialisation MessagePack (que j'utilise) peut ne pas l'être. J'ai donc besoin du code séparé.