异步失败:我应该使用回调,如何通过多个模块传递回调?

时间:2018-11-26 17:13:13

标签: javascript node.js asynchronous socket.io

我正在尝试制作一个简单的文本游戏,该游戏在节点服务器上的socket.io聊天室中运行。该程序的工作方式如下:

目前我有三个主要模块

Rogue:流氓游戏功能的基本主页

rogueParser:负责从命令字符串中提取可行命令的模块

Verb_library:包含可从客户端终端调用的命令列表的模块。

客户端键入诸如“ say hello world”之类的命令。这会触发以下socket.io监听器

socket.on('rg_command', function(command){
  // execute the verb
  let verb = rogueParser(command);
  rogue.executeVerb(verb, command, function(result){
    console.log(result);
  });
});

然后依次从流氓调用executeVerb函数。

  executeVerb: function(verb, data, callback){
    verb_library[verb](data, callback);
  },

如果需要,verb_library中的每个动词应负责操纵数据库,然后将发送回显字符串返回给代表完成操作的适当目标。

编辑:我在发布此文章时选择了“说”,但后来有人指出这是一个糟糕的例子。 “ say”目前尚未异步,但最终将与绝大多数“动词”一样,因为它们将需要调用数据库。

...
  say: function(data, callback){
    var response = {};
    console.log('USR:'+data.user);
    var message = data.message.replace('say','');
    message = ('you say '+'"'+message.trim()+'"');
    response.target = data.user;
    response.type = 'echo';
    response.message = message;
    callback(response);
  },
...

我的问题是

1)我在通过这么多模块传递回调时遇到问题。我应该能够通过多层模块传递回调吗?我担心我是盲人,所以有些作用域魔术使我无法跟踪将回调函数传递给模块时将发生的情况,然后该模块将相同的回调传递给另一个模块,该模块再调用该回调。目前看来,我要么最终要么无法访问回调,要么第一个函数尝试执行而无需等待最终的回调返回null值。

2)Im不知道Im是否通过不使用promise来使其变得比需要的难,或者这完全可以通过回调来实现,在这种情况下,我想学习如何通过这种方式来召集额外的代码。

很抱歉,如果这是一个模糊的问题,我处于对设计模式的怀疑,并且正在寻求有关此常规设置的建议以及有关如何传递这些回调的特定信息。谢谢!

3 个答案:

答案 0 :(得分:1)

回调很好,但是我只能在函数依赖于某些异步结果的情况下使用它们。但是,如果结果立即可用,则应将该函数设计为返回该值。

在您给出的示例中,say不必等待任何异步API调用返回结果,因此我将其签名更改为以下内容:

say: function(data){ // <--- no callback argument 
    var response = {};
    console.log('USR:'+data.user);
    var message = data.message.replace('say','');
    message = ('you say '+'"'+message.trim()+'"');
    response.target = data.user;
    response.type = 'echo';
    response.message = message;
    return response; // <--- return it
}

然后倒退,您还将更改使用say的函数的签名:

executeVerb: function(verb, data){ // <--- no callback argument
    return verb_library[verb](data); // <--- no callback argument, and return the returned value
}

进一步调用堆栈:

socket.on('rg_command', function(command){
    // execute the verb
    let verb = rogueParser(command);
    let result = rogue.executeVerb(verb, command); // <--- no callback, just get the returned value
    console.log(result);
});

当然,这只有在所有动词方法都可以同步返回预期结果的情况下才能起作用。

承诺

如果say 依赖于某些异步API,则可以使用promises。假设此API提供了回调系统,那么您的say函数可以返回如下承诺:

say: async function(data){ // <--- still no callback argument, but async! 
    var response = {};
    console.log('USR:'+data.user);
    var message = data.message.replace('say','');
    response.target = data.user;
    response.type = 'echo';
    // Convert the API callback system to a promise, and use AWAIT
    await respone.message = new Promise(resolve => someAsyncAPIWithCallBackAsLastArg(message, resolve));
    return response; // <--- return it
}

再次向后,您还将更改使用say的函数的签名:

executeVerb: function(verb, data){ // <--- still no callback argument
    return verb_library[verb](data); // <--- no callback argument, and return the returned promise(!)
}

最后:

socket.on('rg_command', async function(command){ // Add async
    // execute the verb
    let verb = rogueParser(command);
    let result = await rogue.executeVerb(verb, command); // <--- await the fulfillment of the returned promise
    console.log(result);
});

答案 1 :(得分:1)

1)通过多层传递回调听起来不是一个好主意。通常我在想会发生什么,如果我能继续这样做一年?它是否足够灵活,以至于当我需要更改架构(比如说客户有新想法)时,我的代码将允许我无需重写整个应用程序?您遇到的情况称为回调地狱。 http://callbackhell.com/ 我们正在尝试做的是使我们的代码尽可能地浅。

2)Promise只是回调的语法糖。但是在Promise中考虑然后在回调中考虑要容易得多。因此,就个人而言,我建议您在项目期间花点时间并尽可能多地掌握编程语言功能。我们执行异步代码的最新方法是使用async / await语法,它使我们能够完全摆脱回调和Promise调用。但是在使用过程中,您必须确定要同时使用两者。

您可以尝试以这种方式完成代码,完成后,找出最大的麻烦以及如何再次编写以避免将来遇到的麻烦。我向你保证,在这里得到明确的回答之前,它将更具教育意义:)

答案 2 :(得分:1)

异步性和JavaScript可以追溯很久了。随着时间的流逝,我们如何处理它,并且有许多应用模式试图使异步变得更容易。我会说有3种具体的流行模式。但是,每个彼此之间都非常相关:

  1. 回调
  2. Promise s
  3. async / await

回调可能是最向后兼容的,只涉及为某些异步任务提供一个函数,以便在任务完成时调用提供的函数。

例如:

/**
 * Some dummy asynchronous task that waits 2 seconds to complete
 */
function asynchronousTask(cb) {
  setTimeout(() => {
    console.log("Async task is done");
    cb();
  }, 2000);
}

asynchronousTask(() => {
  console.log("My function to be called after async task");
});

Promise是一个封装了回调模式的原语,因此您无需为task函数提供函数,而可以在任务返回的then上调用Promise方法:

/**
 * Some dummy asynchronous task that waits 2 seconds to complete
 * BUT the difference is that it returns a Promise
 */
function asynchronousTask() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Async task is done");
      resolve();
    }, 2000);
  });
}

asynchronousTask()
  .then(() => {
    console.log("My function to be called after async task");
  });

最后一个模式是async / await模式,它处理Promise s,它们是回调的封装。它们之所以独特,是因为它们为使用Promises提供了词汇支持,因此您不必直接使用.then(),也不必从任务中显式返回Promise

/*
 * We still need some Promise oriented bootstrap 
 * function to demonstrate the async/await
 * this will just wait a duration and resolve
 */
function $timeout(duration) {
  return new Promise(resolve => setTimeout(resolve, duration));
}

/**
 * Task runner that waits 2 seconds and then prints a message
 */
(async function() {
  await $timeout(2000);
  console.log("My function to be called after async task");
}());


现在我们的词汇表已经清理干净,我们需要考虑另一件事:这些模式都依赖于API。您正在使用的库使用回调。可以混合使用这些模式,但是我会说编写的代码应该一致。选择一种模式,然后包装或与所需的库连接。

如果该库处理回调,请查看是否有包装库或某种机制使它处理Promises。 async / await使用Promises,但不使用回调。