(Предупреждение: этот вопрос абсолютно огромен , потому что проблема имеет действительно сложный контекст. Черт, к тому времени, как я закончу , пишу этот вопрос Я действительно могу в конечном итоге уклониться от резиныЯ сам на самом деле придумаю решение ...)
(Правка: Этого не произошло. Я до сих пор не знаю, что делать. Надеюсь, не существует правила сайта против таких гигантских вопросов, как этот ...... здесь *)
Я пишу код для бота Discord (использующего Node и Discord.js), который взаимодействует с базой данных.(В частности MongoDB.) Конечно, это означает double асинхронное поведение.Когда я пишу вещи самым простым способом, все работает довольно хорошо, и я думаю, что в целом я достаточно хорошо понимаю Обещания, обратные вызовы и await
, чтобы я мог гарантировать, что все происходит в правильной последовательности.
ОднакоПри рефакторинге моего кода для повышения модульности я натолкнулся на кажущееся непреодолимым раздражение: я потерял надлежащий перехват ошибок, и вещи выдают, что они преуспели, пока на одном дыхании модуль, который он выполнил (правильно), сообщает, что командане удалось.
Во-первых, немного фона.
У бота есть ряд команд, которые используют базу данных;мы назовем их «! оскорбление» и «! шутка».Идея этих команд заключается в том, что они процедурно составляют оскорбление или шутку, которые составлены из компонентов, которые пользователи добавили в базу данных.У каждой команды есть отдельная «коллекция» (термин MongoDB, думаю, таблица SQL), содержащая их соответствующие данные, которые были введены пользователями.
Бот изначально был написан кем-то другим, и его решение для добавления и удаления объектов в/ из каждой коллекции должно было быть четыре отдельные команды: "! insultadd", "! insultdelete", "! jokeadd" и "! jokedelete".Моя первая мысль, увидев это, была «модульность, съешь свое сердце. Yikes».Кодовая база содержала большое количество повторяющихся кодов, как это, и поэтому я поставил своей целью абстрагировать функциональность настолько, чтобы можно было устранить большую часть этой избыточности, и в целом кодовую базу было бы намного проще расширять и поддерживать.
ИтакЯ пришел с командой "! Db".Уже есть слой модульности: все, что делает! Db, это вызывает «подкоманды», которые реализуют каждую отдельную функцию.Эти подкоманды называются такими вещами, как «! Dbadd», «! Dbdelete» и т. Д., И они не предназначены для самостоятельного вызова.Важно отметить, что я сначала написал эти подкоманды, и только после того, как все они были независимо работоспособны, я создал! Db, чтобы обернуть их в упрощенной форме, просто используя оператор case.(Например, вызов !db add insultsCollection "ugly"
(где insultsCollection
- набор оскорбительных прилагательных) просто закончится вызовом !dbadd
с соответствующими аргументами.) Итак, первоначально каждая подкоманда выводила результаты самостоятельно,используя строки типа msg.channel.send('Inserted "' + selectedItem + '" into ' + selectedCollection + '.');
.
Первоначально это работало просто отлично.! db не нужно было делать ничего больше, чем просто:
var dbadd = require('../commandsInternal/dbadd.js');
dbadd.execute(msg,args.slice(1),db);
и! dbadd позаботился бы о том, чтобы распечатать пользователю, что операция прошла успешно, сообщив, какой элемент был вставлен в БД.
Однако, важной частью этого гигантского рефакторинга является то, что внешнее поведение и использование остаются в основном одинаковыми для конечного пользователя - то есть! Jokeadd и его родственники останутся, но их внутренние части будут вычерпаны и замененыс вызовами соответствующих функций! дБ.Здесь мы начинаем сталкиваться с неприятностями.Когда я пытаюсь вызвать что-то вроде! Insultadd, это произойдет:
> !insultadd "ugly"
Inserted "ugly" into "insultsCollection". (This is printed by !dbadd.)
The bot can now call you "ugly"! (This is printed by !insultadd.)
Это поведение нежелательно, потому что в основном мы хотим представить пользователю, как если бы это был простой список прилагательных, и поэтому мы хотим избежать ссылок, например, на имена коллекций в БД.Итак, как я это исправил?Я думаю, что наиболее распространенным способом было бы добавить какой-либо флаг к подкомандам, например "beQuiet
", чтобы определить, печатает ли он свои собственные вещи или нет.Если бы это была «нормальная» кодовая база, я бы, наверное, так и сделал.Но ...
Команды написаны в модулях Node, которые экспортируют несколько вещей: имя команды, время восстановления команды и т. Д., Но что наиболее важно, функцию с именем execute(msg, args, db)
,Эта функция - то, как основной поток бота вызывает произвольные команды.Он ищет имя команды, сопоставляет его с объектом, а затем пытается выполнить метод execute
для объекта command
.Обратите внимание, что execute
принимает три аргумента ... объект Discord.js Message
, аргументы команды (массив строк) и объект MongoDB Db
.Чтобы передать флаг типа "beQuiet
" в! Dbadd, я был бы вынужден добавить еще один аргумент к execute
, что мне крайне не хотелось бы делать, потому что это означало бы, что некоторые командыполучить "специальные" аргументы по причинам, и ... тьфу.Это было бы нарушением последовательности, предлагая вещи стать полностью свободными для всех.
Так что я не могу передать флаг.Хорошо что дальше?«Ну, - подумал я, - почему бы мне просто не переместить печать в !db
?»Я так и сделал.Теперь мое заявление о переключении выглядит следующим образом:
switch (choice) {
case "add":
dbadd.execute(msg,args.slice(1),db);
msg.channel.send('Inserted "' + args[2] + '" into ' + args[1] + '.');
break;
case "delete":
dbdelete.execute(msg,args.slice(1),db);
msg.channel.send('"' + args[2] + '" has been removed from ' + args[1] + '.');
break;
// ... etc
}
Хорошо, круто!Итак, давайте выполним это ... хорошо, круто, кажется, работает нормально.Теперь давайте просто протестируем его с каким-то неверным вводом ...
> !db delete insultsCollection asdfasdf
Did the user give a collection that exists? : true (Debugging output)
Error: No matches in given collection. (Correct error output from !dbdelete)
"asda" has been removed from hugs. (Erroneous output from !db)
Э-э-э.Итак, почему это происходит?По сути, это из-за асинхронности.Все вызовы в базу данных требуют от вас либо обратного вызова, либо обработки обещания.(Я предпочитаю последнее, когда это возможно.) Итак, у! Dbdelete есть такие вещи:
var query = { value: { $eq: selectedItem} };
let numOfFind = await db.collection(selectedCollection)
.find(query)
.count();
// Note that .count() returns a Promise that resolves to an int.
// Hence the await.
if (numOfFind == 0) {
msg.channel.send("Error: No matches in given collection.");
return;
}
Удобно, верно?Превращение функции execute()
(в которую входит приведенный выше код) в функцию async
сделало все намного проще для написания.Я использую .then()
, где это уместно, и все в порядке.Но проблема, по сути, заключается в том, что return
...
(Ой, на минуту я подумала, что сама себя подтрунила в решении проблемы. Но, очевидно, просто добавление throw
не работает.)
Хорошо, так ... проблема в том ... использую ли я return
или throw
,! Db не волнует.То, как я об этом думаю, выполнение асинхронного вызова функции (например, db.collection (). Find ()) приводит к запуску независимой «работы».(Я уверен, что я очень ошибаюсь по этому поводу, но эта модель мышления сработала до сих пор.) Видя, что такие вещи, как:
db.collection(selectedCollection).deleteMany(query, function(err, result) {
if (err) {
throw err;
console.log('Something went wrong!');
return;
}
console.log('"' + selectedItem + '" has been removed from ' + selectedCollection + '.');
});
console.log("Success! Deleted the thing.");
на самом деле будут печатать "Успех!"ПЕРЕД фактическим удалением элемента, я пришел к выводу, что сценарий продолжает веселиться, когда вы вызываете что-то асинхронное, и если вы хотите, чтобы это было на самом деле , то напечатайте это впоследствии, вынужно (в случае выше) поместить его в функцию обратного вызова, или использовать .then()
, или await
результат.У вас есть до.
Но проблема в ... из-за модульности! Dbdelete, я не могу ничего из этого сделать.Они не работают:
// Option 1: Callbacks.
// Doesn't work because execute() doesn't take a callback!
case "delete":
dbdelete.execute(msg,args.slice(1),db, function(err, result) {
msg.channel.send('"' + args[2] + '" has been removed from ' + args[1] + '.',msg);
});
break;
// Option 2: .then().
// Doesn't work because execute() doesn't return a Promise!
case "delete":
dbdelete.execute(msg,args.slice(1),db)
.then(function(err, result) {
msg.channel.send('"' + args[2] + '" has been removed from ' + args[1] + '.',msg);
});
break;
// Option 3: await.
// Doesn't work because... I don't really know why but I know it doesn't work.
// Also, again, execute() doesn't return a promise so we can't await it.
case "delete":
await dbdelete.execute(msg,args.slice(1),db);
msg.channel.send('"' + args[2] + '" has been removed from ' + args[1] + '.',msg);
break;
Итак, я в конце своей веревки.Я понятия не имею, как решить это.Честно говоря, я серьезно подумываю о том, чтобы заставить .execute () возвращать Promise, чтобы я мог .then ().Но я действительно не хочу этого делать, тем более что я не знаю как. Короче говоря: есть ли любой способ сделать .then () для функции, которая невернуть обещание?Если бы я мог просто сделать это блокировкой, мы были бы в порядке.
ОБНОВЛЕНИЕ: Вот код для dbdelete.js: https://pastebin.com/LdHm3ybU ОБНОВЛЕНИЕ 2: По словам Марка Мейера, поскольку я использовал ключевое слово await
, execute()
на самом деле действительно возвращает обещание!И оказывается, это решает одну из проблем:
case "delete":
let throwaway = await dbdelete.execute(msg,args.slice(1),db);
message.channel.send('"' + args[2] + '" has been removed from ' + args[1] + '.');
break;
Этот код приводит к более близкому к предполагаемому результату: оператор print по-прежнему всегда выполняется даже при ошибке, но ... тогда я просто заставляю dbdelete.execute()
возвращать логическое значение, которое я установил в false
if! Dbне должен ничего печатать !!Итак, обе проблемы теперь решены!Спасибо всем за столь быстрый ответ!Вы были действительно полезны!<3 </p>