Comment intégrer les appels de fonction asynchrone dans une fonction de synchronisation dans un noeud.js ou Javascript?

supposez que vous mainteniez une bibliothèque qui expose une fonction getData . Vos utilisateurs l'appellent pour obtenir des données réelles:

var output = getData();

Sous le capot les données sont sauvegardées dans un fichier donc vous avez implémenté getData en utilisant le noeud.js intégré fs.readFileSync . Il est évident que getData et fs.readFileSync sont des fonctions de synchronisation. Un jour, on vous a dit de changer la source de données sous-jacente à un repo tel que MongoDB qui ne peut être consulté asynchrone. On vous a aussi dit d'éviter d'énerver vos utilisateurs, L'API getData ne peut pas être modifiée pour retourner simplement une promesse ou exiger un paramètre de rappel. Comment répondez-vous aux deux exigences?

fonction asynchrone utilisant callback/promise est L'ADN de Javasscript et de Node.js. Toute application JS non triviale est probablement imprégnée de ce style de codage. Mais cette pratique peut facilement conduire à ce que l'on appelle la pyramide de la mort. Pire encore, s'il y a un code dans l'un des appelants chaîne d'appel dépend du résultat de la fonction async, ce code doit être enveloppé dans la fonction de rappel ainsi, imposant une contrainte de style de codage à l'appelant. De temps en temps je trouve le besoin d'encapsuler une fonction async (souvent fournie dans une bibliothèque de tiers) dans une fonction de synchronisation afin d'éviter un remaniement global massif. La recherche d'une solution à ce sujet aboutit généralement à des paquets Node Fibers ou des paquets npm qui en dérivent. Mais les fibres ne peuvent pas résolvez le problème auquel je fais face. Même L'exemple fourni par L'auteur de Fibers illustre la déficience:

...
Fiber(function() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);
}).run();
console.log('back in main');

production réelle:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)

si la fonction Fiber transforme réellement la fonction async sleep en sync, la sortie devrait être:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
back in main

j'ai créé un autre exemple simple dans JSFiddle et la recherche de code pour produire la production attendue. J'accepte une solution qui ne fonctionne que dans le Nœud.js ainsi vous êtes libre d'exiger n'importe quel paquet npm malgré le fait qu'il ne fonctionne pas dans JSFiddle.

102
demandé sur abbr 2014-02-17 06:46:11

10 réponses

deasync transforme la fonction async en sync, implémentée avec un mécanisme de blocage en appelant le noeud.boucle d'événement js à la couche JavaScript. En conséquence, deasync ne bloque que le code à venir sans bloquer tout le thread,ni l'inciter à attendre. Avec ce module, voici la réponse au défi jsFiddle:

function AnticipatedSyncFunction(){
  var ret;
  setTimeout(function(){
      ret = "hello";
  },3000);
  while(ret === undefined) {
    require('deasync').runLoopOnce();
  }
  return ret;    
}


var output = AnticipatedSyncFunction();
//expected: output=hello (after waiting for 3 sec)
console.log("output="+output);
//actual: output=hello (after waiting for 3 sec)

(disclaimer: je suis le co-auteur de deasync . Le module a été créé après avoir affiché cette question et trouvé aucune proposition viable.)

94
répondu abbr 2015-02-24 04:31:08

il y a aussi un module de synchronisation npm. qui est utilisé pour synchroniser le processus de l'exécution de la requête.

quand vous voulez exécuter des requêtes parallèles de manière synchrone, alors le noeud restreint pour faire cela parce qu'il n'attend jamais de réponse. et sync module est parfait pour ce genre de solution.

code échantillon

/*require sync module*/
var Sync = require('sync');
    app.get('/',function(req,res,next){
      story.find().exec(function(err,data){
        var sync_function_data = find_user.sync(null, {name: "sanjeev"});
          res.send({story:data,user:sync_function_data});
        });
    });


    /*****sync function defined here *******/
    function find_user(req_json, callback) {
        process.nextTick(function () {

            users.find(req_json,function (err,data)
            {
                if (!err) {
                    callback(null, data);
                } else {
                    callback(null, err);
                }
            });
        });
    }

lien de référence: https://www.npmjs.com/package/sync

5
répondu sanjeev kumar 2016-12-19 06:57:30

Si la fonction de Fibres tourne vraiment asynchrone en fonction de sommeil en sync

Oui. À l'intérieur de la fibre, la fonction attend avant de se connecter ok . Les fibres ne rendent pas les fonctions asynchrones synchrones, mais permettent d'écrire du code synchrone qui utilise des fonctions asynchrones et qui s'exécute ensuite de manière asynchrone à l'intérieur d'un Fiber .

de temps en temps je trouve le besoin d'encapsuler une fonction async dans un fonction de synchronisation afin d'éviter un remaniement global massif.

vous ne pouvez pas. Il est impossible de rendre le code asynchrone synchrone. Vous devrez anticiper cela dans votre code global, et l'écrire dans le style async dès le début. Que vous enveloppiez le code global dans une fibre, utilisez des promesses, des générateurs de promesses, ou des callbacks simples dépend de vos préférences.

Mon objectif est de minimiser l'impact sur l'appelant lorsque la méthode d'acquisition des données passe de sync à async

les promesses et les fibres peuvent faire ça.

4
répondu Bergi 2015-06-02 00:26:38

vous ne devriez pas regarder ce qui se passe autour de l'appel qui crée la fibre, mais plutôt à ce qui se passe à l'intérieur la fibre. Une fois que vous êtes à l'intérieur de la fibre, vous pouvez programmer dans le style sync. Par exemple:

function f1() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);   
}

function f2() {
    f1();
    f1();
}

Fiber(function() {
    f2();
}).run();

dans la fibre que vous appelez f1 , f2 et sleep comme si elles étaient sync.

dans une application web typique, vous créerez la fibre dans votre HTTP demande répartiteur. Une fois que vous avez fait cela, vous pouvez écrire toute la logique de gestion de vos requêtes dans le style sync, même si cela appelle des fonctions asynchrones (fs, bases de données, etc.).

0
répondu Bruno Jouhier 2014-02-17 07:38:50

Noeud De Fabrication.la synchronisation du code js est essentielle dans quelques aspects tels que la base de données. Mais l'avantage réel du noeud.js est en code async. Comme il s'agit d'un fil simple non-blocage.

nous pouvons le synchroniser en utilisant la fibre de fonctionnalité importante() Utiliser attendre () et reporter () nous appelons toutes les méthodes utilisant l'attente(). puis remplacer les fonctions de rappel à reporter().

code Async Normal.Cela utilise des fonctions de rappel.

function add (var a, var b, function(err,res){
       console.log(res);
});

 function sub (var res2, var b, function(err,res1){
           console.log(res);
    });

 function div (var res2, var b, function(err,res3){
           console.log(res3);
    });

Sync ci-dessus code using Fiber (), wait () and defer ()

fiber(function(){
     var obj1 = await(function add(var a, var b,defer()));
     var obj2 = await(function sub(var obj1, var b, defer()));
     var obj3 = await(function sub(var obj2, var b, defer()));

});

j'espère que cela aidera. Merci

0
répondu Mohan Ramakrishna 2015-07-14 15:51:59

Je ne trouve pas de scénario qui ne puisse être résolu en utilisant des fibres de noeud. L'exemple que vous avez fourni en utilisant des fibres de noeud se comporte comme prévu. La clé est de courir le code à l'intérieur d'une fibre, de sorte que vous n'avez pas à démarrer une nouvelle fibre dans des positions aléatoires.

permet de voir un exemple: dire que vous utilisez un cadre, qui est le point d'entrée de votre application (vous ne pouvez pas modifier ce cadre). Ce framework charge les modules nodejs comme plugins, et appelle quelques méthodes sur les plugins. Disons que ce cadre n'accepte que les fonctions synchrones, et n'utilise pas de fibres par lui-même.

Il y a une bibliothèque que vous souhaitez utiliser dans l'un de vos plugins, mais cette bibliothèque est asynchrone, et vous ne voulez pas le modifier.

le fil principal ne peut pas être fourni quand aucune fibre n'est en cours d'exécution, mais vous pouvez encore créer des plugins en utilisant des fibres! Il suffit de créer une entrée wrapper qui démarre tout le cadre à l'intérieur d'un fibre, de sorte que vous pouvez obtenir l'exécution à partir des plugins.

inconvénient: si le framework utilise setTimeout ou Promise en interne, alors il échappera au contexte fiber. Cela peut être fait en se moquant de setTimeout , Promise.then , et de tous les gestionnaires d'événements.

C'est ainsi que vous pouvez produire une fibre jusqu'à ce qu'un Promise soit résolu. Ce code prend une fonction async (retour de promesse) et reprend la fibre lorsque la promesse est résolue:

cadre de saisie.js

console.log(require("./my-plugin").run());

async-lib.js

exports.getValueAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Async Value");
    }, 100);
  });
};

mon-plugin.js

const Fiber = require("fibers");

function fiberWaitFor(promiseOrValue) {
  var fiber = Fiber.current, error, value;
  Promise.resolve(promiseOrValue).then(v => {
    error = false;
    value = v;
    fiber.run();
  }, e => {
    error = true;
    value = e;
    fiber.run();
  });
  Fiber.yield();
  if (error) {
    throw value;
  } else {
    return value;
  }
}

const asyncLib = require("./async-lib");

exports.run = () => {
  return fiberWaitFor(asyncLib.getValueAsync());
};

mon entrée.js

require("fibers")(() => {
  require("./framework-entry");
}).run();

quand vous lancez node framework-entry.js , il lancera une erreur: Error: yield() called with no fiber running . Si vous exécutez node my-entry.js cela fonctionne comme prévu.

0
répondu Tamas Hegedus 2016-09-08 18:18:00

vous devez utiliser des promesses:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async () => {
    return await asyncOperation();
}

const topDog = () => {
    asyncFunction().then((res) => {
        console.log(res);
    });
}

j'aime les définitions de fonction de flèche plus. Mais n'importe quelle chaîne de la forme "() => {...}" pourrait également être écrite comme une "fonction () {...} "

donc topDog n'est pas async malgré l'appel d'une fonction async.

enter image description here

EDIT: je me rends compte que beaucoup de fois vous avez besoin d'envelopper une fonction async dans une fonction de synchronisation est à l'intérieur d'un contrôleur. Pour ces situations, voici un truc de fête:

const getDemSweetDataz = (req, res) => {
    (async () => {
        try{
            res.status(200).json(
                await asyncOperation()
            );
        }
        catch(e){
            res.status(500).json(serviceResponse); //or whatever
        }
    })() //So we defined and immediately called this async function.
}

en utilisant ceci avec des callbacks, vous pouvez faire un wrap qui n'utilise pas de promesses:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async (callback) => {
    let res = await asyncOperation();
    callback(res);
}

const topDog = () => {
    let callback = (res) => {
        console.log(res);
    };

    (async () => {
        await asyncFunction(callback)
    })()
}

en appliquant cette astuce à un EventEmitter, vous pouvez obtenir les mêmes résultats. Définissez L'écouteur de L'émetteur EventEmitter où j'ai défini le callback, et émit l'événement où j'ai appelé le callback.

0
répondu user2485309 2018-05-08 12:53:00

de nos jours ce modèle de générateur peut être une solution dans de nombreuses situations.

voici un exemple d'instructions séquentielles de la console dans nodejs en utilisant async readline.fonction de question:

var main = (function* () {

  // just import and initialize 'readline' in nodejs
  var r = require('readline')
  var rl = r.createInterface({input: process.stdin, output: process.stdout })

  // magic here, the callback is the iterator.next
  var answerA = yield rl.question('do you want this? ', r=>main.next(r))    

  // and again, in a sync fashion
  var answerB = yield rl.question('are you sure? ', r=>main.next(r))        

  // readline boilerplate
  rl.close()

  console.log(answerA, answerB)

})()  // <-- executed: iterator created from generator
main.next()     // kick off the iterator, 
                // runs until the first 'yield', including rightmost code
                // and waits until another main.next() happens
-1
répondu drodsou 2016-08-17 20:36:13

j'ai d'abord eu du mal avec node.js et asynchrone.js est la meilleure bibliothèque que j'ai trouvé pour vous aider à composer avec cela. Si vous voulez écrire du code synchrone avec le noeud, l'approche est de cette façon.

var async = require('async');

console.log('in main');

doABunchOfThings(function() {
  console.log('back in main');
});

function doABunchOfThings(fnCallback) {
  async.series([
    function(callback) {
      console.log('step 1');
      callback();
    },
    function(callback) {
      setTimeout(callback, 1000);
    },
    function(callback) {
      console.log('step 2');
      callback();
    },
    function(callback) {
      setTimeout(callback, 2000);
    },
    function(callback) {
      console.log('step 3');
      callback();
    },
  ], function(err, results) {
    console.log('done with things');
    fnCallback();
  });
}

ce programme produira toujours ce qui suit...

in main
step 1
step 2
step 3
done with things
back in main
-2
répondu Michael Connor 2014-05-01 13:59:31

Javascript est un langage threaded simple, vous ne voulez pas bloquer tout votre serveur! Le code Async élimine les conditions de course en rendant les dépendances explicites.

Apprenez à aimer le code asynchrone!

regardez promises pour le code asynchrone sans créer une pyramide d'enfer de rappel. Je recommande la bibliothèque promiseQ pour noeud.js

httpGet(url.parse("http://example.org/")).then(function (res) {
    console.log(res.statusCode);  // maybe 302
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);  // maybe 200
});

http://howtonode.org/promises

EDIT: c'est de loin ma réponse la plus controversée, node a maintenant le mot-clé yield, qui vous permet de traiter le code async comme s'il était sychrone. http://blog.alexmaccaw.com/how-yield-will-transform-node

-11
répondu roo2 2015-02-27 00:41:59