【问题标题】:node.js can a method be written so it can be called both ways - i.e. with callback or async/await?node.js 可以编写一个方法,以便可以两种方式调用它 - 即使用回调或异步/等待?
【发布时间】:2020-03-31 16:46:19
【问题描述】:

多年来,我创建了一个我在项目中使用的标准方法库。他们中的许多人都有回调。我最近了解了 Promise 和 async/await 以使代码读取同步并喜欢它而不是早期的回调。

对于正在进行的和未来的项目,我想对库中的旧方法使用 async/await。是否可以简单地在它们前面添加“异步”,然后当我调用它们时添加“等待”以使它们承诺友好。然而同时我的旧项目可以使用它们并将它们称为回调?

例如:

var checkIfFileExists = function(filename,callback){

    fs.stat(filename, function(err, stat) {

        if(err == null) {

            let jsonObj =   {   'status'        : true,
                                'error'         : false
                            };

        } else if(err.code == 'ENOENT') {

            console.log( 'File ' + filename + ' does not exists');

            let jsonObj = { 'status'        : false,
                            'error'     :   err
                        };

        } else {

            let jsonObj =   { 'status'        : false,
                                'error'     : err
                            };
        }

        callback(jsonObj);


    });

};

对于这种方法,我可以像这样简单地添加“异步”吗:

var checkIfFileExists = async function(filename,callback){ .... }

然后我的旧项目将其称为回调:

 checkIfFileExists(fileToCheck, function(jsonResponse){
      // do something with response
 });

新项目称之为:

let jsonResponse = await checkIfFileExists(fileToCheck);

【问题讨论】:

    标签: node.js promise async-await


    【解决方案1】:

    通过以返回承诺的方式基本上包装使用标准回调参数的现有函数,有各种用于“承诺”的库。这可能对你有用。

    例如:https://www.npmjs.com/package/es6-promisify

    const {promisify} = require("es6-promisify");
    const checkIfFileExistsPromise = promisify(checkFileIfExists);
    
    const exists = await checkIfFileExistsPromise(...);
    

    另请注意,fs 目前在 Node.js 中内置了一个基于 Promise 的 API:https://nodejs.org/api/fs.html#fs_fs_promises_api

    【讨论】:

    • 谢谢布拉德。感谢迅速的反应。我也会调查一下。这可能适用于我的一些旧方法。我确实将@jfriend00 的答案标记为正确,因为它非常全面。
    【解决方案2】:

    首先,即使是旧代码也可以很好地使用 Promise。几乎任何版本的 node.js 或浏览器都可以轻松地填充 Promise,调用者可以使用 .then().catch() 就好了。因此,我的第一个论点是,现在是向前推进并抛弃旧的回调样式的时候了。使用 Promise 不会阻止任何人使用您的代码,它只会迫使他们将自己的编程知识转移到正确的十年。

    如果您确实想在同一个界面中同时提供回调和承诺选项,那么您不能只将async 放在函数上并让一切正常。这不是async 所做的。它没有那种程度的超能力。另外,如果您希望与旧版本的 node.js 兼容,那么该版本可能也不支持async

    制作可以以任何一种方式调用的东西的通常方法是检测回调是否被传递,并且代码根据是否传递进行调整,返回一个与代码完成挂钩的承诺,如果未传递回调,如果传递则使用回调。

    所以,如果你有一个接口checkIfFileExists(...),你可以像这样使用它:

     // with callback
     checkIfFileExists("myfile.txt", function(err, exists) {
         if (err) {
             console.log(err);
         } else {
             console.log(exists);
         }
     });
    
     // with promise
     checkIfFileExists("myfile.txt").then(function(exists) {
         console.log(exists);
     }).catch(function(err) {
         console.log(err);
     });
    

    还有,这是一个实现:

    const fs = require('fs');
    const promisify = require('util').promisify;
    
    function makePromisify(fn) {
        if (fn._p) {
           return fn._p;
        } else {
           fn._p = promisify(fn);
           return fn._p;
        }
    }
    
    // if not called with callback, then returns a promise
    function checkIfFileExists(filename, callback) {
        if (!callback) {
            return makePromisify(checkIfFileExists)(filename);
        } else {
            fs.stat(filename, function(err, stats) {
               if (err) {
                   if (err.code === 'ENOENT') {
                       callback(null, false);
                   } else {
                       callback(err);
                   }
               } else {
                  callback(null, true);
               }
            });
        }
    }
    

    此实现使用util.promisify()(在节点 v8.0 中添加)自动为您制作回调接口的承诺版本。如果你想支持足够老的 node.js 版本,以至于 util.promisify() 甚至不存在,那么也可以用几行代码手动构建。

    出于效率原因,它会在第一次使用时将函数的承诺版本缓存为被调用函数的._p 属性,因此在后续调用中,它可以使用完全相同的函数承诺版本。

    请注意,我更喜欢首先针对 Promise 接口进行优化的设计,因为这是未来更有可能使用的设计,因为那是 Javascript 语言的未来并且应该是更常见的用途。但是,对于这样的函数,您需要使用 fs.promises 接口到 fs 模块,并且至少假定节点 v10.0。


    如果您可以假设在最新的节点 v10,那么使用 fs.promises 接口会变得更简单一些,并且对于 Promise 的使用会更加精简:

    const fsp = require('fs').promises;
    
    // if not called with callback, then returns a promise
    function checkIfFileExists(filename, callback) {
    
        async function _checkIfFilesExists(filename) {
            try {
                await fsp.stat(filename);
                return true;
            } catch(err) {
                if (err.code === 'ENOENT') {
                    return false;
                } else {
                    throw err;
                }
            }
        }
    
        if (!callback) {
            return _checkIfFileExists(filename);
        } else {
            _checkIfFileExists(filename).then(result => {
               callback(null, result);
           }).catch(callback);
        }
    }
    

    【讨论】:

    • 太棒了!谢谢!当我开始阅读您的回答时,我意识到我可以很容易地检查回调是否通过。但是后来我看到了您推荐的方法,它们看起来像是要走的路。这些将非常适合我的图书馆。再次感谢!
    • @Curious101 - 我刚刚修复了 makePromisify() 函数中的一个小错误,所以请确保您获得了最新版本。
    • 谢谢@jfriend00。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 2019-05-22
    • 2013-04-08
    • 2016-02-23
    • 1970-01-01
    相关资源
    最近更新 更多