Асинхронные фрустрации: я должен использовать обратные вызовы и как мне передать их через несколько модулей? - PullRequest
0 голосов
/ 26 ноября 2018

Я пытаюсь создать простую текстовую игру, которая работает в чате socket.io на сервере узлов.Программа работает следующим образом:

В настоящее время у меня есть три основных модуля

Rogue: базовый дом функций мошеннических игр

rogueParser: модуль, отвечающий за извлечение выполнимых команд из командных строк

Verb_library: модуль, содержащий список команд, которые могут быть вызваны из клиентского терминала.

Клиент вводит команду типа «поздоровайся с миром».Это запускает следующий слушатель 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) У меня проблемы с передачей обратных вызовов через множество модулей.Должен ли я быть в состоянии передать обратный вызов через несколько уровней модулей?Я боюсь, что я слепой, поэтому какая-то магия, которая заставляет меня потерять отслеживание того, что должно произойти, когда я передаю функцию обратного вызова в модуль, который затем передает тот же самый обратный вызов другому модулю, который затем вызывает обратный вызов.В настоящее время кажется, что я либо не получаю доступ к обратному вызову в конце, либо первая функция пытается выполнить, не дожидаясь окончательного обратного вызова, возвращающего нулевое значение.

2) Я не уверен, что я делаю это сложнее, чем нужно, не используя обещания, или это полностью достижимо с помощью обратных вызовов, и в этом случае я хочу узнать, как сделать это таким образом, прежде чем вызыватьдополнительный код

Извините, если это расплывчатый вопрос, я нахожусь в положении сомнений в шаблоне проектирования и ищу советы по этой общей настройке, а также конкретную информацию о том, как эти обратные вызовы должны передаваться.Спасибо!

Ответы [ 3 ]

0 голосов
/ 26 ноября 2018

1) Пропуск обратного вызова через несколько слоев не кажется хорошей идеей.Обычно я думаю, что произойдет, если я продолжу делать это в течение года?Будет ли он достаточно гибким, чтобы когда мне нужно было перейти на архитектуру (скажем, у клиента появилась новая идея), мой код позволит мне без переписывания всего приложения?То, что вы испытываете, называется адом обратного вызова.http://callbackhell.com/ То, что мы пытаемся сделать, - это сделать наш код как можно более мелким.

2) Promise - это просто синтаксический сахар для обратного вызова.Но гораздо проще думать в Promise, чем в обратном вызове.Так что лично я бы посоветовал вам не торопиться и понять как можно больше возможностей языка программирования во время вашего проекта.Последний способ выполнения асинхронного кода - использование синтаксиса async / await, который позволяет полностью избавиться от обратных вызовов и вызовов Promise.Но на вашем пути вам наверняка придется поработать с обоими.

Вы можете попытаться закончить свой код таким образом, и когда вы закончите, найдите, что было самой большой болью и как вы могли бы написать это сновачтобы избежать этого в будущем.Я обещаю вам, что это будет гораздо более познавательно, чем получить откровенный ответ здесь:)

0 голосов
/ 26 ноября 2018

Асинхронность и 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");
});

Обещания - это примитив, который инкапсулирует шаблон обратного вызова, так что вместо предоставления функции для функции задачи вы вызываете метод 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, которые являются инкапсуляцией обратных вызовов.Они уникальны, потому что предоставляют лексическую поддержку для использования Обещаний, поэтому вам не нужно использовать .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 потребляет обещания, но не обратные вызовы.

0 голосов
/ 26 ноября 2018

Обратные вызовы в порядке, но я бы использовал их, только если функция зависит от какого-то асинхронного результата.Однако, если результат доступен сразу же, функция должна быть спроектирована так, чтобы возвращало это значение.

В приведенном вами примере say не нужно ждать какого-либо асинхронного APIcall to return с результатом, поэтому я бы изменил его сигнатуру на следующую:

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, то вы можете использовать обещания.Давайте предположим, что этот 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);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...