Cómo envolver llamadas a funciones asincrónicas en una función de sincronización en el nodo.js o Javascript?


Supongamos que mantiene una biblioteca que expone una función getData. Sus usuarios lo llaman para obtener datos reales:
var output = getData();
Bajo el capó los datos se guardan en un archivo por lo que implementó getData utilizando el nodo.js built-in fs.readFileSync. Es obvio que tanto getData como fs.readFileSync son funciones de sincronización. Un día se le dijo que cambiara la fuente de datos subyacente a un repositorio como MongoDB al que solo se puede acceder de forma asíncrona. También se le dijo que evitara molestar a sus usuarios, getData La API no se puede cambiar para devolver simplemente promise o demand un parámetro de devolución de llamada. ¿Cómo se cumplen ambos requisitos?

La función asíncrona usando callback/promise es el ADN de JavasScript y Node.js. Cualquier aplicación JS no trivial es probablemente impregnado con este estilo de codificación. Pero esta práctica puede conducir fácilmente a la llamada pirámide de la perdición. Peor aún, si cualquier código en cualquier persona que llama en la cadena de llamadas depende del resultado de la función async, ese código también tiene que estar envuelto en la función de devolución de llamada, imponiendo un restricción de estilo de codificación en la persona que llama. De vez en cuando encuentro la necesidad de encapsular una función asincrónica (a menudo proporcionada en una biblioteca de terceros) en una función de sincronización con el fin de evitar la re-factorización global masiva. La búsqueda de una solución sobre este tema generalmente terminaba con Fibras de nodo o paquetes npm derivados de él. Pero las fibras no pueden resolver el problema que estoy enfrentando. Incluso el ejemplo proporcionado por el autor de Fibers ilustró la deficiencia:

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

Reales salida:

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 función Fiber realmente convierte el sueño de la función asíncrona en sincronización, la salida debería ser:

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

He creado otro ejemplo simple en JSFiddle y buscando código para obtener la salida esperada. Aceptaré una solución que solo funciona en Node.js por lo que es libre de requerir cualquier paquete npm a pesar de no funcionar en JSFiddle.

Author: abbr, 2014-02-17

10 answers

Deasync convierte la función async en sync, implementada con un mecanismo de bloqueo llamando a Node.bucle de eventos js en la capa JavaScript. Como resultado, deasync solo bloquea la ejecución del código posterior sin bloquear el hilo completo, ni incurrir en una espera ocupada. Con este módulo, aquí está la respuesta al desafío 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)

(descargo de responsabilidad: Soy el coautor de deasync. El módulo fue creado después de publicar esta pregunta y no encontró ninguna propuesta viable.)

 94
Author: abbr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-24 04:31:08

También hay un módulo de sincronización npm. que se utiliza para sincronizar el proceso de ejecución de la consulta.

Cuando desea ejecutar consultas paralelas de manera sincrónica, entonces node restrict para hacerlo porque nunca espera la respuesta. y el módulo de sincronización es muy perfecto para ese tipo de solución.

Código de muestra

/*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);
                }
            });
        });
    }

Enlace de Referencia: https://www.npmjs.com/package/sync

 5
Author: sanjeev kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-19 06:57:30

Si la función de fibra realmente convierte la función asincrónica sleep en sync

Sí. Dentro de la fibra, la función espera antes de registrar ok. Las fibras no hacen que las funciones asíncronas sean síncronas, sino que permiten escribir código de aspecto síncrono que utiliza funciones asíncronas y luego se ejecutará de forma asíncrona dentro de un Fiber.

De vez en cuando encuentro la necesidad de encapsular una función asincrónica en una función de sincronización con el fin de evitar la masiva global re-factorización.

No puedes. Es imposible hacer que el código asíncrono sea síncrono. Tendrá que anticiparse a eso en su código global y escribirlo en estilo asíncrono desde el principio. Ya sea que envuelva el código global en una fibra, use promesas, generadores de promesas o simples devoluciones de llamada depende de sus preferencias.

Mi objetivo es minimizar el impacto en la persona que llama cuando el método de adquisición de datos se cambia de sync a async

Ambas promesas y fibras puedo hacer eso.

 4
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-02 00:26:38

No deberías mirar lo que sucede alrededor de la llamada que crea la fibra, sino más bien lo que sucede dentro de la fibra. Una vez que esté dentro de la fibra, puede programar en estilo de sincronización. Por ejemplo:

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

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

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

Dentro de la fibra que llamas f1, f2 y sleep como si estuvieran sincronizados.

En una aplicación web típica, creará la fibra en su despachador de solicitudes HTTP. Una vez que lo hayas hecho, puedes escribir toda tu lógica de manejo de solicitudes en estilo de sincronización, incluso si llama a funciones asincrónicas(fs, bases de datos, etc.).

 0
Author: Bruno Jouhier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-17 07:38:50

Haciendo Nodo.la sincronización de código js es esencial en algunos aspectos como la base de datos. Pero la ventaja real de Nodo.js se encuentra en código asíncrono. Ya que es de un solo hilo sin bloqueo.

Podemos sincronizarlo usando Fibra de funcionalidad importante() Usar await () y diferir () llamamos a todos los métodos usando await (). luego reemplace las funciones de devolución de llamada con defer ().

Código asíncrono normal.Esto utiliza funciones de devolución de llamada.

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

Sincroniza el código anterior usando Fiber(), await() y 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()));

});

Espero esto ayudará. Gracias

 0
Author: Mohan Ramakrishna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-14 15:51:59

No puedo encontrar un escenario que no se pueda resolver usando fibras de nodo. El ejemplo que proporcionó usando node-fibers se comporta como se espera. La clave es ejecutar todo el código relevante dentro de una fibra, para que no tenga que iniciar una nueva fibra en posiciones aleatorias.

Veamos un ejemplo: Digamos que usas algún framework, que es el punto de entrada de tu aplicación (no puedes modificar este framework). Este framework carga módulos nodejs como plugins, y llama a algunos métodos en los plugins. Digamos que este marco solo acepta funciones síncronas, y no utiliza fibras por sí mismo.

Hay una biblioteca que quieres usar en uno de tus plugins, pero esta biblioteca es asincrónica, y tampoco quieres modificarla.

El hilo principal no se puede producir cuando no se está ejecutando ninguna fibra, ¡pero aún puede crear complementos utilizando fibras! Simplemente cree una entrada de envoltura que inicie todo el marco dentro de una fibra, para que pueda obtener la ejecución desde el plugin.

Inconveniente: Si el marco utiliza setTimeout o Promises internamente, entonces escapará al contexto de fibra. Esto se puede resolver burlándose setTimeout, Promise.then, y todos los controladores de eventos.

Así que así es como puedes producir una fibra hasta que se resuelva un Promise. Este código toma una función async (Promise returning) y reanuda la fibra cuando la promesa es resuelto:

Marco-entrada.js

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

Async-lib.js

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

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

Mi entrada.js

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

Cuando ejecutas node framework-entry.js lanzará un error: Error: yield() called with no fiber running. Si ejecuta node my-entry.js funciona como se espera.

 0
Author: Tamas Hegedus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-08 18:18:00

Tienes que usar promesas:

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

Me gustan más las definiciones de funciones de flecha. Pero cualquier cadena de la forma" () = > {...} "también podría escribirse como" function () {...}"

Así que topDog no es async a pesar de llamar a una función async.

introduzca la descripción de la imagen aquí

EDITAR: Me doy cuenta de que muchas de las veces que necesita envolver una función asincrónica dentro de una función de sincronización está dentro de un controlador. Para esas situaciones, aquí hay un truco de fiesta:

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

Utilizando esto con callbacks, puedes hacer un wrap que no use promesas:

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

Aplicando este truco a un EventEmitter, puedes obtener los mismos resultados. Definir el oyente del EventEmitter donde he definido la devolución de llamada, y emitir el evento donde llamé a la devolución de llamada.

 0
Author: user2485309,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-08 12:53:00

Hoy en día este patrón generador puede ser una solución en muchas situaciones.

Aquí un ejemplo de instrucciones de consola secuenciales en nodejs usando async readline.función de pregunta:

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
Author: drodsou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-17 20:36:13

Luché con esto al principio con node.js y async.js es la mejor biblioteca que he encontrado para ayudarte a lidiar con esto. Si desea escribir código síncrono con node, el enfoque es de esta manera.

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

Este programa SIEMPRE producirá lo siguiente...

in main
step 1
step 2
step 3
done with things
back in main
 -2
Author: Michael Connor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-01 13:59:31

Javascript es un único lenguaje de subprocesos, ¡no quieres bloquear todo tu servidor! El código asincrónico elimina las condiciones de carrera al hacer explícitas las dependencias.

¡Aprende a amar el código asíncrono!

Echa un vistazo a promises para el código asincrónico sin crear una pirámide de infierno de devolución de llamada. Recomiendo la biblioteca promiseQ para node.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

EDITAR: esta es, con mucho, mi respuesta más controvertida, node ahora tiene la palabra clave yield, que le permite tratar el código asíncrono como si fuera sychronous. http://blog.alexmaccaw.com/how-yield-will-transform-node

 -11
Author: roo2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-27 00:41:59