278 votes

Appeler une fonction Javascript asynchrone de manière synchrone

Tout d'abord, il s'agit d'un cas très spécifique où l'on s'est trompé de méthode dans le but d'intégrer un appel asynchrone dans une base de code très synchrone qui compte plusieurs milliers de lignes et pour laquelle le temps ne permet pas actuellement d'effectuer les changements nécessaires pour "bien faire les choses". Cela fait mal à chaque fibre de mon être, mais la réalité et les idéaux ne s'accordent pas toujours. Je sais que ça craint.

OK, ceci étant dit, comment faire pour que je puisse.. :

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

Les exemples (ou leur absence) utilisent tous des bibliothèques et/ou des compilateurs, qui ne sont pas viables pour cette solution. J'ai besoin d'un exemple concret de la façon dont on peut le faire bloquer (par exemple, ne PAS laisser la fonction doSomething jusqu'à ce que le callback soit appelé) SANS geler l'interface utilisateur. Si une telle chose est possible en JS.

17 votes

Il n'est tout simplement pas possible de bloquer un navigateur et d'attendre. Ils ne le feront tout simplement pas.

2 votes

Javascript n'a pas de mécanisme de blocage sur la plupart des navigateurs... vous devrez créer un callback qui sera appelé à la fin de l'appel asynchrone pour retourner les données.

0 votes

Je ne pense pas que cela puisse être fait. Javascript (malgré les web workers) est monofilaire - le callback ne peut pas être appelé avant le retour du gestionnaire d'événement actuel. Cela nécessiterait l'équivalent de la fonction async/await de C# en Javascript et je doute qu'une telle bête existe. Je crains que vous ne soyez obligé de réécrire votre code en CPS, aussi ennuyeux que cela puisse être.

6voto

Ce que vous voulez est réellement possible maintenant. Si vous pouvez exécuter le code asynchrone dans un travailleur de service, et le code synchrone dans un travailleur web, alors vous pouvez faire en sorte que le travailleur web envoie un XHR synchrone au travailleur de service, et pendant que le travailleur de service fait les choses asynchrones, le thread du travailleur web attendra. Cette approche n'est pas géniale, mais elle peut fonctionner.

1voto

Maxim Kulikov Points 532

Dans Node.js, il est possible d'écrire du code synchrone qui invoque en fait des opérations asynchrones. fibres nodales permet cela. Il s'agit d'une extension native tierce partie fournie sous forme de module npm. Elle implémente des fibres/coroutines, de sorte que lorsqu'une fibre spécifique est bloquée en attendant une opération asynchrone, la boucle d'événements du programme entier ne se bloque pas - une autre fibre (si elle existe) continue son travail.

Avec des fibres, votre code ressemblerait à ceci :

var Fiber = require('fibers');

function doSomething() {
  var fiber = Fiber.current;

  function callBack(data) {
    fiber.run(data);
  }

  myAsynchronousCall(param1, callBack);

  // execution blocks here
  var data = Fiber.yield();
  return data;
}

// The whole program must be wrapped with Fiber
Fiber(function main() {

  var data = doSomething();
  console.log(data);

}).run();

Notez que vous devriez l'éviter et utiliser async/await à la place. Voir ci-dessous une note du readme du projet https://github.com/laverdet/node-fibers :

NOTE D'OBSOLESCENCE -- L'auteur de ce projet vous recommande d'éviter son utilisation si possible. La version originale de ce module ciblait nodejs v0.1.x au début de 2011, lorsque JavaScript sur le serveur avait un aspect très différent. Depuis lors async/await , Promesses y Générateurs ont été normalisés et l'écosystème dans son ensemble a évolué dans ce sens.

Je continuerai à supporter les nouvelles versions de nodejs aussi longtemps que possible, mais la v8 et nodejs sont des plateformes extraordinairement complexes et dynamiques. Il est inévitable qu'un jour cette bibliothèque cesse brusquement de fonctionner et que personne ne puisse rien y faire.

Je tiens à remercier tous les utilisateurs de Fibers, votre soutien au fil des ans a été très important pour moi.

0voto

david Points 5559

Pas possible, j'en ai peur. Votre seul espoir serait de lancer l'appel ajax plus tôt, et d'arrêter toute la chaîne synchrone jusqu'au retour de l'appel ajax.

0voto

ninjagecko Points 25709

Une chose que les gens pourraient ne pas considérer : Si vous contrôlez la fonction asynchrone (dont d'autres morceaux de code dépendent), ET que le chemin de code qu'elle prendrait n'est pas nécessairement asynchrone, vous pouvez la rendre synchrone (sans casser ces autres morceaux de code) en créant un paramètre optionnel.

Actuellement :

async function myFunc(args_etcetc) {
    // you wrote this
    return 'stuff';
}

(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:' result);
})()

Pensez-y :

function myFunc(args_etcetc, opts={}) {
    /*
        param opts :: {sync:Boolean} -- whether to return a Promise or not
    */
    var {sync=false} = opts;
    if (sync===true)
        return 'stuff';
    else
        return new Promise((RETURN,REJECT)=> {
            RETURN('stuff');
        });
}

// async code still works just like before:
(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:', result);
})();
// prints: 'stuff'

// new sync code works, if you specify sync mode:
(function main() {
    var result = myFunc('argsetcetc', {sync:true});
    console.log('sync result:', result);
})();
// prints: 'stuff'

Bien sûr, cela ne fonctionne pas si la fonction asynchrone repose sur des opérations intrinsèquement asynchrones (requêtes réseau, etc.), auquel cas l'effort est vain (sans attendre effectivement sans raison).

De plus, c'est assez laid de renvoyer soit une valeur, soit une promesse en fonction des options passées.

("Pourquoi aurais-je écrit une fonction asynchrone si elle n'utilisait pas de constructions asynchrones ?" pourrait-on se demander ? Peut-être que certaines modalités/paramètres de la fonction nécessitent l'asynchronisme et d'autres non, et qu'en raison de la duplication du code, vous vouliez un bloc monolithique plutôt que des morceaux de code modulaires séparés dans différentes fonctions... Par exemple, peut-être que l'argument est soit localDatabase (qui ne nécessite pas d'attendre) ou remoteDatabase (ce qui est le cas). Alors vous pourriez avoir une erreur d'exécution si vous essayez de faire {sync:true} sur la base de données distante. Ce scénario est peut-être révélateur d'un autre problème, mais voilà).

0voto

pont Points 499

L'utilisation des fils de travail de Node 16 rend cela possible. Dans l'exemple suivant, le fil principal exécute le code asynchrone tandis que le fil de travail l'attend de manière synchrone.

Non pas que ce soit très utile, mais cela fait au moins vaguement ce que la question originale demandait en attendant du code asynchrone de manière synchrone.

const {
    Worker, isMainThread, parentPort, receiveMessageOnPort
} = require('worker_threads');
if (isMainThread) {
    const worker = new Worker(__filename);
    worker.on('message', async () => {
        worker.postMessage(await doAsyncStuff());
    });
} else {
    console.log(doStuffSync());
}

function doStuffSync(){
    parentPort.postMessage({fn: 'doStuff'});
    let message;
    while (!message) {
        message = receiveMessageOnPort(parentPort)
    }
    return message;
}

function doAsyncStuff(){
    return new Promise((resolve) => setTimeout(() => resolve("A test"), 1000));
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X