Retrollamada en nodejs?

En el siguiente código ¿estoy en callbackhell? ¿Cómo superar ese escenario sin usar ningún módulo asíncrono en javascript puro?

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

El código anterior se copia en múltiples ubicaciones para que el código funcione como se espera.

 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()); } 

Sí, estás en el infierno de callback. La solución suponiendo que no quiere usar asincrónico (que dudo que pueda justificar aparte de los prejuicios) consiste en:

1 ) Hacer más funciones de nivel superior. Cada función debe realizar 1 o 2 operaciones de E / S como regla general.

2 ) Llame a esas funciones, haciendo que su código siga un patrón de una larga lista de funciones de núcleo corto organizadas en lógica empresarial mediante una pequeña lista de funciones de control de flujo de “pegamento”.

En lugar de:

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

Aspirar a:

 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 ) Usa más argumentos de funciones en lugar de depender tanto de los cierres

4 ) ¡Emite eventos y DESCUBRA TU CÓDIGO! ¿Ves cómo andaste código escribiendo cosas en la base de datos y luego construyendo un correo electrónico y agregándolo a una cola? ¿No ves que esos dos no necesitan existir uno encima del otro? Los correos electrónicos se prestan muy bien a una lógica empresarial central que emite eventos y un módulo de correo electrónico que escucha esos eventos y pone en cola el correo.

5 ) Desacoplar el código de conexión de servicio de nivel de aplicación de la lógica de negocio de transacción específica. El tratamiento de las conexiones a los servicios de red debe manejarse de manera más amplia y no debe integrarse con un conjunto específico de lógica comercial.

6 ) Lea otros módulos para ejemplos

En cuanto a si debe utilizar una biblioteca asíncrona, puede y debe tomar una decisión al respecto, DESPUÉS de que sepa, y sepa muy bien, todos y cada uno de estos enfoques:

  1. devoluciones de llamada y técnicas básicas de javascript funcionales
  2. eventos
  3. promesas
  4. Bibliotecas de ayuda (asíncrona, paso, ágil, etc.)

Cualquier desarrollador serio de node.js sabe cómo usar y trabajar dentro de TODOS esos paradigmas. Sí, todos tienen su enfoque predilecto y tal vez algo de furia acerca de los enfoques no favorecidos, pero ninguno de estos es difícil y es malo establecer su decisión sin poder señalar algún código no trivial que haya escrito desde cero en cada paradigma. Además, debes probar varias bibliotecas de ayudantes y comprender cómo funcionan y por qué te van a ahorrar un texto repetitivo. Estudiar el trabajo de Tim Caswell’s Step o Caolan McMahon’s async va a ser muy esclarecedor. ¿Has visto el uso del código fuente de everyauth de las promesas? No me gusta personalmente pero seguramente tengo que admitir que el autor ha exprimido casi hasta la última repetición de esa biblioteca, y la forma en que usa las promesas convertirá tu cerebro en un pretzel. Estas personas son magos con mucho que enseñar. No te burles de esas bibliotecas solo por puntos hipster o lo que sea.

También un buen recurso externo es callbackhell.com .

“Si tratas de codificar el inicio de sesión de bussiness db usando node.js puro, vas directamente al retrol de callback”

Recientemente, creé una abstracción simple llamada WaitFor para llamar a las funciones asincrónicas en el modo de sincronización (basado en Fibras): https://github.com/luciotato/waitfor

verifique el ejemplo de la base de datos:

Ejemplo de base de datos (pseudocódigo)

puro node.js (infierno de callback leve):

 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); } 

Nota: El código anterior, aunque parece que captará las excepciones, no lo hará . La captura de excepciones con callback agrega mucho dolor, y no estoy seguro de si tendrá el parámetro 'res' para responder al usuario. Si a alguien le gusta arreglar este ejemplo ... sea mi invitado.

usando 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); } 

Nota: Las excepciones serán atrapadas como se esperaba. Se llamarán los métodos db (db.select, db.execute) con this = db

Tu codigo

Para usar wait.for, tendrás que ESTANDARIZAR TUS CALLBACKS para funcionar (err, data)

Si NORMALIZA SUS CALLBACKS , su código podría verse así:

 //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); 

¿ver? no hay infierno de callback

He puesto otra solución en mi blog . Es feo, pero es lo más legible que podría hacer con javascript puro.

 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; } } }