【问题标题】:Splitting a node-style callback into two callbacks将节点样式的回调拆分为两个回调
【发布时间】:2017-01-10 11:42:19
【问题描述】:

我正在整理一个示例,展示如何将简单的同步 Node.js 程序转换为使用 async/await 的异步版本。应该有几个中间步骤,从普通的基于回调的版本开始,然后是使用两个回调的一个,一个用于正常(解决)情况,另一个用于错误(拒绝)情况,然后会导致承诺。

每个版本的工作是创建一个空文件夹 copy(可能已经存在并且可能包含文件)并复制所有文件(称为 file1.txtfile2.txt)在 orig 文件夹中。如果错误发生在任何应该被明确捕获的地方,打印到控制台并且程序不应继续执行。

带有正常错误优先回调的版本可以正常工作,但我遇到了拆分回调版本的问题。它只复制file2.txt,而不复制file1.txt。

这是我用于转换 fs 函数的代码:

const fs = require('fs');

fs.exists = function(path, callback) {
    fs.stat(path, (err, stats) => {
        if (err) {
            callback(null, false);
        } else {
            callback(null, true);
        }
    });
};

function splitCallback(f) {
    return (...params) => {
        reject  = params[params.length - 2];
        resolve = params[params.length - 1];
        params  = params.slice(0, params.length - 2);
        f(...params, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    };
}

const sfs = {};
const functionNames = ['exists', 'readdir', 'unlink', 'mkdir', 'readFile', 'writeFile'];
for (const functionName of functionNames) {
    sfs[functionName] = splitCallback(fs[functionName].bind(fs));
}

这是使用这些函数的实际示例:

function handleError(err) {
    console.error(err);
}

function initCopyDirectory(callback) {
    sfs.exists('copy', handleError, exists => {
        if (exists) {
            sfs.readdir('copy', handleError, filenames => {
                let fileCount = filenames.length;
                if (fileCount === 0) {
                    callback();
                }
                for (const filename of filenames) {
                    sfs.unlink(`copy/${filename}`, handleError, () => {
                        fileCount--;
                        if (fileCount === 0) {
                            callback();
                        }
                    });
                }
            });
        } else {
            sfs.mkdir('copy', handleError, () => callback);
        }
    });
}

function copyFiles() {
    // sfs.readdir('orig', handleError, filenames => {
    //     for (const filename of filenames) {
    //         console.log(filename);
    //         sfs.readFile(`orig/${filename}`, handleError, data => {
    //             console.log('reading', filename);
    //             sfs.writeFile(`copy/${filename}`, data, handleError, () => {
    //                 console.log('writing', filename);
    //             });
    //         });
    //     }
    // });
    sfs.readdir('orig', handleError, filenames => {
        for (const filename of filenames) {
            fs.readFile(`orig/${filename}`, (err, data) => {
                if (err) {
                    handleError(err);
                } else {
                    sfs.writeFile(`copy/${filename}`, data, handleError, () => {});
                }
            });
        }
    });
}

function main() {
    initCopyDirectory(copyFiles);
}

main();

正如这里所写,它可以正常工作(使用适用于 Windows 的 Node 版本 7.4.0),但是当我在 copyFiles 函数中交换 cmets(从而更改 readFile)时,只复制了一个文件,我得到以下输出:

file1.txt
file2.txt
reading file2.txt
writing file2.txt
writing file2.txt

有什么问题?

【问题讨论】:

    标签: javascript node.js asynchronous callback ecmascript-6


    【解决方案1】:

    试试这个而不是注释代码:

         for (const filename of filenames) {
             (function(filename){
                 console.log(filename);
                 sfs.readFile(`orig/${filename}`, handleError, data => {
                     console.log('reading', filename);
                     sfs.writeFile(`copy/${filename}`, data, handleError, () => {
                         console.log('writing', filename);
                     });
                 });
             })(filename)
         }
    

    问题是您在 for 循环中运行异步函数并期望它们同步运行。在调用sfs.writeFile 时(在执行sfs.readFile 之后),for 循环早已执行完毕,因此您只剩下最后一个文件名file2。通过将 for 循环中的所有内容包装在一个闭包中,您可以保持正确的值。

    这是一个更简单的例子:

    for (var i = 0; i < 10 ; i++) {
       setTimeout(function(){
           console.log(i)
       }, 100)
    } 
    

    将打印10 10 次,因为到超时执行时(0.1 秒),for 循环已经完成,而下面的代码将打印数字 0 到 9,因为原始值由闭包保留。 (自己试试)

    for (var i = 0; i < 10 ; i++) {
       (function(i){
           setTimeout(function(){
               console.log(i)
           }, 100)
       })(i)
    } 
    

    【讨论】:

    • 我一开始也是这么想的,但在这里我得到了相同的结果。根据this,我应该在每次迭代的 for-of 循​​环中为 const 声明的变量重新绑定。
    • @Ignavia “新绑定”是什么意思。使用 const 不会改变你不能在 for 循环中保留异步函数中的绑定的事实,这总是比任何异步函数都快
    • 以稍微改变的形式使用您的简单示例:const a = [1,2,3,4,5,6,7,8,9,10]; for (var i of a) { setTimeout(function(){ console.log(i); }, 100); } 产生十乘以 10 作为输出,而 const a = [1,2,3,4,5,6,7,8,9,10]; for (const i of a) { setTimeout(function(){ console.log(i); }, 100); } 产生从 1 到 10 的数字。在链接下有一个更好的格式版本第一条评论。
    【解决方案2】:

    问题是我忘记将 const 放在 splitCallback 中的变量声明前面。这使它们成为不断被覆盖的全局变量。相反,激活严格模式会引发错误。这是正确的代码:

    function splitCallback(f) {
        return (...params) => {
            const input             = params.slice(0, params.length - 2);
            const [reject, resolve] = params.slice(params.length - 2);
            f(...input, (err, ...output) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(...output);
                }
            });
        };
    }
    

    【讨论】:

      猜你喜欢
      • 2020-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-31
      • 1970-01-01
      • 2018-09-14
      • 2012-05-29
      相关资源
      最近更新 更多