以同步方式执行异步调用

Posted

技术标签:

【中文标题】以同步方式执行异步调用【英文标题】:Executing asynchronous calls in a synchronous manner 【发布时间】:2011-08-22 12:31:33 【问题描述】:

在过去的几个小时里,我一直在试图解决这个问题,但无法弄清楚。我想我还是得习惯函数式编程风格;)

我编写了一个遍历目录结构并对某些文件执行操作的递归函数。该函数使用异步 IO 方法。现在我想在整个遍历完成后执行一些操作。

如何确保在所有parse 调用都已执行但仍使用异步 IO 函数后执行此操作?

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

【问题讨论】:

async 似乎是目前处理这个问题最常用的模块。 如果你觉得 aynsc、deferred 或 step 太重而无法使用,像我一样,使用finish。免责声明:我是完成的创造者。 finish 现在是 node-finish,所以上面的链接被破坏了。 github.com/chaoran/node-finish 要真正使异步调用同步,请查看How to wrap async function calls into a sync function in Node.js or javascript?。您似乎正在寻找具有同步外观的控制流库。 【参考方案1】:

寻找Step module。它可以链接异步函数调用并将结果从一个传递到另一个。

【讨论】:

谢谢。我现在正在使用异步模块。似乎是一个非常方便的库。 许多人认为,使用诸如 step 或 node-seq 之类的“链接”库会妨碍开发人员了解 node.js 的异步、I/O 驱动特性。由于我在 node 中看到的绝大多数编程错误都是由于对 node 的软件设计模型缺乏了解。 另见 Q 和 deferred 项目,它们基于 deferred/promise 概念,在我看来它比简单的函数链更强大。【参考方案2】:

这样的事情会起作用——对代码的基本更改是循环变成了一个递归调用,它消耗一个列表直到它完成。这使得添加外部回调成为可能(解析完成后您可以在其中进行一些处理)。

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

【讨论】:

如果我正确理解了您的代码,一旦处理了初始目录中的所有文件(files 为空),就会调用回调。但是子目录中的文件呢?不应该将它们添加到files 数组中吗? 只有在深度优先的方式递归搜索所有子目录时才会调用外部回调。每个对 parse 的内部调用都提供了自己的回调,当它被调用时,它会继续处理当前级别。这段代码运行——试一试,看看它做了什么。【参考方案3】:

推荐使用node-seq https://github.com/substack/node-seq

由 npm 安装。

我正在使用它,我喜欢它..

【讨论】:

【参考方案4】:

看看你的原始代码的修改,它可以在没有异步帮助器库的情况下做你想做的事情。

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!!!');
);

【讨论】:

【参考方案5】:

参见以下解决方案,它使用deferred 模块:

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

【讨论】:

【参考方案6】:

寻找node-sync,一个简单的库,允许您以同步方式调用任何异步函数。主要好处是它使用 javascript-native 设计 - Function.prototype.sync 函数,而不是您需要学习的繁重 API。另外,通过 node-sync 同步调用的异步函数不会阻塞整个进程——它只会阻塞当前线程!

【讨论】:

【参考方案7】:

我一直在使用syncrhonize.js 并取得了巨大的成功。甚至还有一个挂起的拉取请求(效果很好)来支持具有多个参数的异步函数。比 node-sync imho 更好也更容易使用。额外的好处是它具有易于理解和详尽的文档,而 node-sync 没有。

支持连接同步的两种不同方法,一种延迟/等待模型(就像@Mariusz Nowak 建议的那样)和一种更苗条但不是粒度函数目标的方法。每个文档都非常简单。

【讨论】:

【参考方案8】:

你可以使用异步模块。它的自动功能很棒。如果你有函数 A() 和函数 B() 和函数 C() 。函数 B() 和 C() 都依赖于使用函数 A() 的值返回的函数 A()。使用异步模块函数,您可以确保函数 B 和 C 仅在函数 A 执行完成时才会执行。

参考: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
            
        )
    );

在上面的示例中,一旦函数 A 执行完成,函数 B 和函数 C 将一起执行。因此functionB和functionC将同时执行

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

在上面一行中,我们使用 'A' 传递函数 A 的返回值

只有在functionB和functionC执行完成时才会执行functionD。

如果任何函数出现错误,则其他函数的执行将被终止,下面的函数将被执行。您可以在其中编写成功和失败的逻辑。

function (err, results) 

成功执行所有函数“结果”将包含 async.auto 中定义的所有函数的结果

function (err, results) 

【讨论】:

以上是关于以同步方式执行异步调用的主要内容,如果未能解决你的问题,请参考以下文章

nginxswoole高并发原理初探

同步与异步,阻塞与非阻塞

同步调用与异步调用

python 37 同步异步调用

如何在tornado中以异步的方式调用同步函数

同步异步阻塞非阻塞四种调用方式