Обратный звонок в nodejs?

В приведенном ниже коде я в callbackhell? Как преодолеть такой сценарий без использования каких-либо асинхронных модhive в чистом javascript?

emailCallBack(e_data, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } 

Вышеприведенный код копируется в нескольких местах, чтобы сделать работу кода, как ожидалось.

 function processInviteEmails(email_list, user_id, emailCallBack, completionCallback){ function checkEmail(email){ try { check(email).isEmail(); //is valid email checkConnected(email, user_id, function(connect_status, user_row, user_meta_row, connect_row){ var e_data; //insert to connect and send msg to queue if(connect_status === 'not connected'){ var cur_date = moment().format('YYYY-MM-DD'); var dbData = { "first_name": '', "last_name": '', "email": email, "user_id": user_id, "status": "invited", "unsubscribe_token": crypto.randomBytes(6).toString('base64'), "created": cur_date, "modified": cur_date }; ConnectModel.insert(dbData, function(result){ if (result.insertId > 0) { //send to email queue //Queue Email MailTemplateModel.getTemplateData('invitation', function(res_data){ if(res_data.status === 'success'){ var unsubscribe_hash = crypto.createHash("md5") .update(dbData.unsubscribe_token + email) .digest('hex'); var unsubscribe_link = app.locals.SITE_URL+'/unsubscribe/' + result.insertId + '/' + unsubscribe_hash; var template_row = res_data.template_row; var user_full_name = user_row.user_firstname+' '+ user_row.user_lastname; var invitation_link = 'http://'+user_row.url_alias+'.'+ app.locals.SITE_DOMAIN; var mailOptions = { "type": 'invitation', "to": dbData.email, "from_name" : user_full_name, "subject": template_row.message_subject .replace('[[USER]]', user_full_name), "text": template_row.message_text_body .replace('[[USER]]', user_full_name) .replace('[[INVITATION_LINK]]', invitation_link) .replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link), "html": template_row.message_body .replace('[[USER]]', user_full_name) .replace('[[INVITATION_LINK]]', invitation_link) .replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link) }; mailOptions = JSON.stringify(mailOptions); //send email to queue sqsHelper.addToQueue(cfg.sqs_invitation_url, mailOptions, function(data){ if(data){ e_data = null; } else{ e_data = new Error('Unable to Queue '); } emailCallBack(e_data, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } }); } else{ e_data = new Error('Unable to get email template'); emailCallBack(e_data, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } } }); } else{ e_data = new Error('Unable to Insert connect'); emailCallBack(e_data, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } } }); } else{ e_data = new Error('Already connected'); emailCallBack(e_data, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } } }); } catch (e) { //invalid email emailCallBack(e, email); if (email_list.length) { checkEmail(email_list.pop()); } else { completionCallback(); } } } checkEmail(email_list.pop()); } 

Да, вы находитесь в обратном ад. Решение, предполагающее, что вы не хотите использовать async (который, я сомневаюсь, вы можете оправдать, кроме предрассудков), состоит из:

1 ) Сделайте больше функций верхнего уровня. Каждая функция должна выполнять либо 1, либо 2 операции ввода-вывода, как правило.

2 ) Вызвать эти функции, чтобы ваш код соответствовал шаблону длинного списка коротких основных функций, организованных в бизнес-логику, небольшим списком функций «клея» streamа управления.

Вместо:

 saveDb1 //lots of code saveDb2 //lots of code sendEmail //lots of code 

Стремиться к:

 function saveDb1(arg1, arg2, callback) {//top-level code} function saveDb2(arg1, arg2, callback) {//top-level code} function sendEmail(arg1, arg2, callback) {//top-level code} function businessLogic(){//uses the above to get the work done} 

3 ) Используйте больше аргументов функции вместо того, чтобы так полагаться на замыкания

4 ) Выпустите события и ПОЛУЧИТЕ СВОЙ КОД! Посмотрите, как вы вложили материал для написания кода в базу данных, а затем создаете электронное письмо и добавляете его в очередь? Разве вы не видите, как эти два не должны существовать друг над другом? Письма отлично зарекомендовали себя на основных событиях, связанных с бизнес-логикой, и модуль электронной почты, который слушает эти события и ставит в очередь почту.

5 ) Развяжите код подключения службы уровня приложения от конкретной бизнес-логики транзакций. Работа с подключениями к сетевым службам должна обрабатываться более широко и не внедряться в определенный набор бизнес-логики.

6 ) Чтение других модhive для примеров

Что же касается использования асинхронной библиотеки, вы можете и должны сами подумать об этом, но ПОСЛЕ того, как вы знаете, и хорошо знаете, каждый из этих подходов:

  1. обратные вызовы и базовые функциональные методы javascript
  2. Мероприятия
  3. обещания
  4. Вспомогательные библиотеки (асинхронные, шаговые, проворные и т. Д.)

Любой серьезный разработчик node.js знает, как использовать и работать в рамках ВСЕХ тех парадигм. Да, у каждого свой подход и, возможно, некоторый ботаник в отношении неприемлемых подходов, но ни один из них не является сложным, и плохо принять решение, не указывая на какой-то нетривиальный код, который вы написали с нуля в каждой парадигме. Кроме того, вы должны попробовать несколько вспомогательных библиотек и понять, как они работают, и почему они собираются сохранить ваш шаблон. Изучение работы Step Тима Касуэлла или асана Каолана МакМэхона будет очень полезным. Вы видели, как everyauth из них использует обещания? Мне это не нравится лично, но я, конечно же, должен признать, что автор сжал чертовски почти каждый последний бит повторения из этой библиотеки, и то, как он использует обещания, превратит ваш мозг в крендель. Эти люди – мастера, которые многому учат. Не издевайтесь над этими библиотеками только за хипстерские очки или что-то еще.

Также хорошим внешним ресурсом является callbackhell.com .

«Если вы попытаетесь ввести код bussiness db login с помощью pure node.js, вы сразу перейдете к обратному аду»

Недавно я создал простую абстракцию WaitFor для вызова асинхронных функций в режиме синхронизации (на основе Fibers): https://github.com/luciotato/waitfor

проверьте пример базы данных:

Пример базы данных (псевдокод)

pure node.js (мягкий обратный ад):

 var db = require("some-db-abstraction"); function handleWithdrawal(req,res){ try { var amount=req.param("amount"); db.select("* from sessions where session_id=?",req.param("session_id"),function(err,sessiondata) { if (err) throw err; db.select("* from accounts where user_id=?",sessiondata.user_ID),function(err,accountdata) { if (err) throw err; if (accountdata.balance < amount) throw new Error('insufficient funds'); db.execute("withdrawal(?,?),accountdata.ID,req.param("amount"), function(err,data) { if (err) throw err; res.write("withdrawal OK, amount: "+ req.param("amount")); db.select("balance from accounts where account_id=?", accountdata.ID,function(err,balance) { if (err) throw err; res.end("your current balance is " + balance.amount); }); }); }); }); } catch(err) { res.end("Withdrawal error: " + err.message); } 

Примечание. Вышеприведенный код, хотя похоже, что он поймает исключения, он не будет . Захват исключений с помощью обратного вызова ад добавляет много боли, и я не уверен, что у вас будет параметр «res» для ответа на пользователя. Если кто-то захочет исправить этот пример ... будь моим гостем.

используя wait.for :

 var db = require("some-db-abstraction"), wait=require('wait.for'); function handleWithdrawal(req,res){ try { var amount=req.param("amount"); sessiondata = wait.forMethod(db,"select","* from session where session_id=?",req.param("session_id")); accountdata= wait.forMethod(db,"select","* from accounts where user_id=?",sessiondata.user_ID); if (accountdata.balance < amount) throw new Error('insufficient funds'); wait.forMethod(db,"execute","withdrawal(?,?)",accountdata.ID,req.param("amount")); res.write("withdrawal OK, amount: "+ req.param("amount")); balance=wait.forMethod(db,"select","balance from accounts where account_id=?", accountdata.ID); res.end("your current balance is " + balance.amount); } catch(err) { res.end("Withdrawal error: " + err.message); } 

Примечание. Исключения будут проверяться как ожидалось. db методы (db.select, db.execute) будут вызваны с этим = db

Ваш код

Чтобы использовать wait.for, вам нужно СТАНДАРТИЗИРОВАТЬ ВАШИ ЗВОНКИ для функции (err, data)

Если вы СТАНДАРТИЗАЦИЯ ВАШИХ ЗВОНКОВ , ваш код может выглядеть так:

 //run in a Fiber function processInviteEmails(email_list, user_id, emailCallBack, completionCallback){ while (email_list.length) { var email = email_list.pop(); try { check(email).isEmail(); //is valid email or throw var connected_data = wait.for(checkConnected,email,user_id); if(connected_data.connect_status !== 'not connected') throw new Error('Already connected'); //insert to connect and send msg to queue var cur_date = moment().format('YYYY-MM-DD'); var dbData = { "first_name": '', "last_name": '', "email": email, "user_id": user_id, "status": "invited", "unsubscribe_token": crypto.randomBytes(6).toString('base64'), "created": cur_date, "modified": cur_date }; result = wait.forMethod(ConnectModel,'insert',dbData); // ConnectModel.insert shuold have a fn(err,data) as callback, and return something in err if (data.insertId <= 0) //send to email queue //Queue Email res_data = wait.forMethod(MailTemplateModel,'getTemplateData','invitation'); // MailTemplateModel.getTemplateData shuold have a fn(err,data) as callback // inside getTemplateData, callback with err=new Error('Unable to get email template') if (data.status !== 'success') var unsubscribe_hash = crypto.createHash("md5") .update(dbData.unsubscribe_token + email) .digest('hex'); var unsubscribe_link = app.locals.SITE_URL+'/unsubscribe/' + result.insertId + '/' + unsubscribe_hash; var template_row = res_data.template_row; var user_full_name = user_row.user_firstname+' '+ user_row.user_lastname; var invitation_link = 'http://'+user_row.url_alias+'.'+ app.locals.SITE_DOMAIN; var mailOptions = { "type": 'invitation', "to": dbData.email, "from_name" : user_full_name, "subject": template_row.message_subject .replace('[[USER]]', user_full_name), "text": template_row.message_text_body .replace('[[USER]]', user_full_name) .replace('[[INVITATION_LINK]]', invitation_link) .replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link), "html": template_row.message_body .replace('[[USER]]', user_full_name) .replace('[[INVITATION_LINK]]', invitation_link) .replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link) }; mailOptions = JSON.stringify(mailOptions); //send email to queue ... callback(err,data) wait.forMethod(sqsHelper,'addToQueue',cfg.sqs_invitation_url, mailOptions); } catch (e) { // one of the callback returned err!==null emailCallBack(e, email); } } // loop while length>0 completionCallback(); } // run the loop in a Fiber (keep node spinning) wait.launchFiber(processInviteEmails,email_list, user_id, emailCallBack, completionCallback); 

видеть? нет обратного ада

Я поставил другое решение в своем блоге . Это уродливо, но это самая читаемая вещь, которую я мог бы сделать с чистым javascript.

 var flow1 = new Flow1( { execute_next_step: function(err) { if (err) { console.log(err); }; } } ); flow1.execute_next_step(); function Flow1(parent_flow) { this.execute_next_step = function(err) { if (err) return parent_flow.execute_next_step(err); if (!this.next_step) this.next_step = 'START'; console.log('Flow1:', this.next_step); switch (this.next_step) { case 'START': this.next_step = 'FIRST_ASYNC_TASK_FINISHED'; firstAsyncTask(this.execute_next_step.bind(this)); break; case 'FIRST_ASYNC_TASK_FINISHED': this.firstAsyncTaskReturn = arguments[1]; this.next_step = 'ANOTHER_FLOW_FINISHED'; this.another_flow = new AnotherFlow(this); this.another_flow.execute_next_step(); break; case 'ANOTHER_FLOW_FINISHED': this.another_flow_return = arguments[1]; this.next_step = 'FINISH'; this.execute_next_step(); break; case 'FINISH': parent_flow.execute_next_step(); break; } } } function AnotherFlow(parent_flow) { this.execute_next_step = function(err) { if (err) return parent_flow.execute_next_step(err); if (!this.next_step) this.next_step = 'START'; console.log('AnotherFlow:', this.next_step); switch (this.next_step) { case 'START': console.log('I dont want to do anything!. Calling parent'); parent_flow.execute_next_step(); break; } } } 
Давайте будем гением компьютера.