【问题标题】:Make forEach asynchronous in JavaScript在 JavaScript 中使 forEach 异步
【发布时间】:2014-03-12 09:09:01
【问题描述】:

我试图理解 Node.js 的异步编程,但在这段代码上停滞不前。

回调函数中的这个函数返回一个目录中的文件数组:

function openDir(path, callback) {
    path = __dirname + path;
    fs.exists(path, function (exists) {
        if (exists) {
            fs.readdir(path, function (err, files) {
                if (err) {
                    throw err;
                }
                var result = [];
                files.forEach(function (filename, index) {
                    result[index] = filename;
                });
                return callback(result);
            });
        }
    });
}

但是当我在.forEach 内部使用异步代码时,它什么也没有返回:

function openDir(path, callback) {
    path = __dirname + path;
    fs.exists(path, function (exists) {
        if (exists) {
            fs.readdir(path, function (err, files) {
                if (err) {
                    throw err;
                }
                var result = [];
                files.forEach(function (filename, index) {
                    fs.stat(path + filename, function (err, stats) {
                        if (err) {
                            throw err;
                        }
                        result[index] = filename;
                    });
                });
                return callback(result);
            });
        }
    });
}

我明白为什么会这样,但不明白如何编写正确的代码。

【问题讨论】:

  • 不能从异步函数返回,必须设置回调
  • 这个模块会让你的生活更轻松:github.com/caolan/async
  • 回调或承诺/延迟模式是您的选择。
  • 问题是fs.stat 也是异步的。
  • @PaulS。他的代码中已经有一个callback,但被滥用了。

标签: javascript node.js asynchronous foreach


【解决方案1】:

问题在于 fs.stat 也是异步的,但您可能会执行以下操作:

var result = [],
    expectedLoadCount = files.length,
    loadCount = 0;

files.forEach(function (filename, index) {
    fs.stat(path + filename, function (err, stats) {
        if (err) {
            throw err;
        }
        result[index] = filename;
        if (++loadCount === expectedLoadCount) callback(result);
    });
});

【讨论】:

    【解决方案2】:

    其他答案可能效果很好,但它们目前在语义上与原始代码完全不同:它们都并行执行stats,而不是顺序执行。 forEach 将启动与文件列表中的文件一样多的异步stats 操作。这些操作的完成顺序可能与列表的原始顺序完全不同。这可能会严重影响错误处理逻辑。

    以下方法实现了一个状态机,旨在异步执行stats,但顺序(未经测试):

    function openDir(path, callback) {
        path = __dirname + path;
        fs.exists(path, function (exists) {
            if (!exists)
                callback(null, null); // node (err, result) convention
            else {
                fs.readdir(path, function (err, files) {
                    if (err)
                        callback(err, null); // node (err, result) convention
                    else {
                        var results = [];
                        var i = 0;
                        nextStep(); // process the first file (the first step)
    
                        function nextStep() {
                            if (i >= files.length) // no more files?
                                callback(null, result); // node (err, result) convention
                            else {
                                fs.stat(path + files[i], function (err, stats) {
                                    if (err)
                                        callback(err, null); // node (err, result) convention
                                    else {
                                        results[i++] = stats;
                                        // proceed to the next file
                                        nextStep();
                                    }
                                });
                            }
                        }
                    }
                }
            }
        });                   
    });
    

    Promises 可能有助于减少像上面那样著名的"Pyramid of Doom" 的嵌套级别。

    【讨论】:

    【解决方案3】:

    试试这个:

    function openDir(path, callback) {
        path = __dirname + path;
        fs.exists(path, function (exists) {
            var totalFiles = 0;;
            if (exists) {
                fs.readdir(path, function (err, files) {
                    if (err) {
                        throw err;
                    }
                    var result = [];
                    files.forEach(function (filename, index) {
                        fs.stat(path + filename, function (err, stats) {
                            if (err) {
                                throw err;
                            }
                            result[index] = filename;
                            totalFiles++;
                            if(totalFiles === files.length){
                                callback(result);
                            }
                        });
                    });
                });
            }
        });
    }
    

    您也可以使用Async module 来帮助解决这类情况

    【讨论】:

    • 2分钟前我打败了你=P
    • 我在项目的许多部分都使用异步。它真的很有用,是有史以来最好的节点包之一。
    猜你喜欢
    • 2012-06-16
    • 1970-01-01
    • 2017-02-10
    • 2019-04-20
    • 1970-01-01
    • 2020-07-26
    • 2017-10-22
    • 2022-01-12
    • 2012-05-21
    相关资源
    最近更新 更多