javascript – Nodejs – How to use external variables in asynchronous functions

Question:

I'm starting at node and I still find the concept of asynchronous functions complicated. I'm using and js to render templates for email in my application.

The prototype is:

"use strict";
const nodemailer = require('nodemailer');
const path = require('path');
const ejs = require('ejs');

function EmailManager(){
  this.configs ={};
  this.receivers = "";
  this.subject = "";
  this.template="";
  this.context ={};

};

EmailManager.prototype.send = function(){

    ejs.renderFile(path.join(__dirname,'..','templates',this.template),this.context, function(err, data) {

        if(err){
                 throw err;
        }

        var transporter = nodemailer.createTransport(this.configs);

        var mailOptions = {
                from: '"'+ this.configs.sender 
                + ' <'+ this.configs.user +'>', // sender address
                to: this.receivers, // list of receivers
                subject: this.subject, 
              }; 

        mailOptions.html = data;


        transporter.sendMail(mailOptions, function(error, info){
              if(error){
                 throw error;
              }
              //console.log('Message sent to '+ this.receivers+", subject: "+ this.subject);
        });

     });

};

To execute, I create a new instance, set all the built-in variables, and then execute send() . However, when I run, the following error message appears:

error TypeError: Cannot read property 'configs' of undefined (node:9820) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): TypeError: Cannot read property 'configs' of undefined

Apparently the this reference is lost inside the asynchronous function, I read that functions of type disregard the external context. So how could I pass this reference to my method? I also had this same problem with other similar functions.

Answer:

As you suggested the execution context (the this ) is not what you expect. There are some tools to fix this. The two places where this can happen is in the .send() function itself and then inside the ejs.renderFile method callback.

Assuming that the context of this inside .send() is correct (if you don't have to show how you're running that code), you can do two ways to force this to the EmailManager instance inside the callback:

Using .bind() :

ejs.renderFile(path.join(__dirname, '..', 'templates', this.template), this.context, function(err, data) {
    // ... o código dentro da callback
}.bind(this)); // <--- usando ".bind()"

Using self , a reference to this :

You can create a variable with the name self to be a reference, pointer, of the instance of EmailManager . In this case it would be like this:

EmailManager.prototype.send = function() {
    var self = this; // <-- agora podes usar o "self" em vêz do "this"
    
    ejs.renderFile(path.join(__dirname, '..', 'templates', self.template), self.context, function(err, data) {
        if (err) throw err;

        var transporter = nodemailer.createTransport(self.configs);
        var mailOptions = {
            from: '"' + self.configs.sender + ' <' + self.configs.user + '>', // sender address
            to: self.receivers, // list of receivers
            subject: self.subject,
        };
        mailOptions.html = data;
        transporter.sendMail(mailOptions, function(error, info) {
            if (error) throw error;
            console.log('Message sent to '+ self.receivers+", subject: "+ self.subject);
        });
    });
};
Scroll to Top