Ejecutar llamadas asíncronas de manera síncrona

He estado tratando de entender este problema durante las últimas horas, pero no puedo resolverlo. Supongo que todavía tengo que acostumbrarme al estilo de programación funcional;)

Escribí una función recursiva que atraviesa una estructura de directorio y hace cosas con ciertos archivos. Esta función utiliza los métodos de E / S asíncronos. Ahora quiero realizar alguna acción cuando todo este recorrido esté terminado.

¿Cómo me aseguraría de que esta acción se realice después de todo? parse ¿Se han realizado llamadas pero todavía utilizan las funciones de E / S asíncronas?

var fs = require('fs'),
    path = require('path');

function parse(dir) {
    fs.readdir(dir, function (err, files) {
        if (err) {
            console.error(err);
        } else {                
            // f = filename, p = path
            var each = function (f, p) {
                return function (err, stats) {
                    if (err) {
                        console.error(err);
                    } else {
                        if (stats.isDirectory()) {
                            parse(p);
                        } else if (stats.isFile()) {
                            // do some stuff
                        }
                    }
                };
            };

            var i;
            for (i = 0; i < files.length; i++) {
                var f = files[i];
                var p = path.join(dir, f);
                fs.stat(p, each(f, p));
            }
        }
    });
}

parse('.');

// do some stuff here when async parse completely finished

preguntado el 10 de mayo de 11 a las 14:05

async parece ser el módulo más utilizado para lidiar con esto actualmente. -

Si siente que aynsc, diferido o paso es demasiado pesado para usar, como yo, use acabado. Descargo de responsabilidad: soy el creador del acabado. -

finish es ahora node-finish, por lo que el enlace anterior está roto. github.com/chaoran/node-finish -

Para realmente hacer que las llamadas asincrónicas sean síncronas, eche un vistazo a ¿Cómo envolver las llamadas a funciones asíncronas en una función de sincronización en Node.js o Javascript?. Parece que busca bibliotecas de flujo de control de aspecto sincrónico. -

8 Respuestas

Busque Módulo de paso. Puede encadenar llamadas de funciones asincrónicas y pasar resultados de una a otra.

contestado el 10 de mayo de 11 a las 18:05

Gracias. Ahora estoy usando el módulo async. Parece ser una biblioteca bastante útil. - Sven Jacobs

Muchos creen que el uso de bibliotecas de "encadenamiento" como step o node-seq impide que un desarrollador aprecie la naturaleza asincrónica, impulsada por E / S de node.js. Dado que la gran mayoría de los errores de programación que he visto en node se deben a la falta de comprensión del modelo de diseño de software de node. - Rob Raisch

Véase también Q y diferido proyectos, se basan en el concepto diferido / promesa que, en mi opinión, es mucho más poderoso que el simple encadenamiento de funciones. - Mariusz Nowak

Podrías usar el módulo async. Su función automática es impresionante. Si tiene la función A () y la función B () y la función C (). Tanto la función B () como la C () dependen de la función A () que está usando el retorno de valor de la función A (). al usar la función de módulo asíncrono, puede asegurarse de que la función B y C se ejecutarán solo cuando se complete la ejecución de la función A.

Ref: https://github.com/caolan/async

async.auto({
            A: functionA(){//code here },
            B: ['A',functionB(){//code here }],
            C: ['A',functionC(){//code here }],
            D: [ 'B','C',functionD(){//code here }]
        }, function (err, results) {
              //results is an array that contains the results of all the function defined and executed by async module
              // if there is an error executing any of the function defined in the async then error will be sent to err  and as soon as err will be produced execution of other function will be terminated
            }
        })
    });

En el ejemplo anterior, functionB y functionC se ejecutarán juntas una vez que se complete la ejecución de la función A. Por lo tanto, functionB y functionC se ejecutarán simultáneamente

functionB: ['A',functionB(){//code here }]

En la línea anterior estamos pasando el retorno de valor por functionA usando 'A'

y functionD se ejecutará solo cuando se complete la ejecución de functionB y functionC.

Si hay un error en alguna función, entonces la ejecución de otra función se terminará y se ejecutará la siguiente función, donde puede escribir su lógica de éxito y fracaso.

function (err, results) {}

En la ejecución exitosa de todas las funciones, los "resultados" contendrán el resultado de todas las funciones definidas en async.auto

function (err, results) {}

Respondido el 16 de diciembre de 14 a las 12:12

Eche un vistazo a la modificación de su código original que hace lo que quiere sin las bibliotecas auxiliares asíncronas.

var fs = require('fs'),
    path = require('path');

function do_stuff(name, cb)
{
    console.log(name);
    cb();
}

function parse(dir, cb) {
    fs.readdir(dir, function (err, files) {
        if (err) {
            cb(err);
        } else {             

            // cb_n creates a closure
            // which counts its invocations and calls callback on nth
            var n = files.length;
            var cb_n = function(callback)
            {
                return function() {
                    --n || callback();
                }
            }

            // inside 'each' we have exactly n cb_n(cb) calls
            // when all files and dirs on current level are proccessed, 
            // parent cb is called

            // f = filename, p = path
            var each = function (f, p) {
                return function (err, stats) {
                    if (err) {
                        cb(err);
                    } else {
                        if (stats.isDirectory()) {
                            parse(p, cb_n(cb));
                        } else if (stats.isFile()) {
                            do_stuff(p+f, cb_n(cb));
                            // if do_stuff does not have async 
                            // calls inself it might be easier 
                            // to replace line above with
                            //  do_stuff(p+f); cb_n(cb)();
                        }
                    }
                };
            };

            var i;
            for (i = 0; i < files.length; i++) {
                var f = files[i];
                var p = path.join(dir, f);
                fs.stat(p, each(f, p));
            }
        }
    });
}

parse('.', function()
{
    // do some stuff here when async parse completely finished
    console.log('done!!!');
});

contestado el 11 de mayo de 11 a las 06:05

Algo como esto funcionaría: el cambio básico en su código es el bucle convertido en una llamada recursiva que consume una lista hasta que se hace. Eso hace posible agregar una devolución de llamada externa (donde puede hacer algún procesamiento después de que se realiza el análisis).

var fs = require('fs'),
  path = require('path');

function parse(dir, cb) {
    fs.readdir(dir, function (err, files) {
        if (err)
          cb(err);
        else 
          handleFiles(dir, files, cb);
    });
}

function handleFiles(dir, files, cb){
  var file = files.shift();
  if (file){
    var p = path.join(dir, file);
    fs.stat(p, function(err, stats){
      if (err)
        cb(err);
      else{
        if (stats.isDirectory())
          parse(p, function(err){
            if (err)
              cb(err);
            else
              handleFiles(dir, files, cb);
          });
        else if (stats.isFile()){
          console.log(p);
          handleFiles(dir, files, cb);
        }
      }
    })
  } else {
    cb();
  }

}


parse('.', function(err){
  if (err)
    console.error(err);
  else {
    console.log('do something else');
  }
});

contestado el 10 de mayo de 11 a las 19:05

Si entiendo su código correctamente, se llamará a la devolución de llamada tan pronto como se hayan procesado todos los archivos del directorio inicial (files esta vacio). Pero, ¿qué pasa con los archivos en subdirectorios? ¿No deberían agregarse al files ¿formación? - Sven Jacobs

La devolución de llamada externa solo se llamará cuando todos los subdirectorios se hayan buscado de forma recursiva en profundidad. Cada llamada interna para analizar proporciona su propia devolución de llamada, que continúa procesando el nivel actual cuando se llama. Este código se ejecuta, pruébelo y vea qué hace. - Geoff Chappell

Vea la siguiente solución, usa diferido módulo:

var fs   = require('fs')
  , join = require('path').join
  , promisify = require('deferred').promisify

  , readdir = promisify(fs.readdir), stat = promisify(fs.stat);

function parse (dir) {
    return readdir(dir).map(function (f) {
        return stat(join(dir, f))(function (stats) {
            if (stats.isDirectory()) {
                return parse(dir);
            } else {
                // do some stuff
            }
        });
    });
};

parse('.').done(function (result) {
    // do some stuff here when async parse completely finished
});

Respondido el 28 de diciembre de 13 a las 11:12

He estado usando syncrhonize.js con gran acierto. Incluso hay una solicitud de extracción pendiente (que funciona bastante bien) para admitir funciones asíncronas que tienen múltiples parámetros. Mucho mejor y más fácil de usar que la sincronización de nodos en mi humilde opinión. Una ventaja adicional es que tiene una documentación completa y fácil de entender, mientras que la sincronización de nodos no la tiene.

Admite dos métodos diferentes para conectar la sincronización, un modelo defered / await (como lo que sugirió @Mariusz Nowak) y un enfoque de objetivo de función más delgado aunque no tan granular. Los documentos son bastante sencillos para cada uno.

Respondido el 19 de enero de 14 a las 23:01

Recomendar usar node-seq https://github.com/substack/node-seq

instalado por npm.

Lo estoy usando y me encanta ...

contestado el 11 de mayo de 11 a las 01:05

Busque sincronización de nodos, una biblioteca simple que le permite llamar a cualquier función asíncrona de forma síncrona. El principal beneficio es que utiliza un diseño nativo de javascript, función Function.prototype.sync, en lugar de API pesadas que necesitará aprender. Además, la función asíncrona que se llamó sincrónicamente a través de la sincronización de nodos no bloquea todo el proceso, ¡bloquea solo el hilo actual!

Respondido 08 Oct 12, 16:10

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.