1

I have the following code:

"use strict";
const Raven = require("raven");
Raven.config(
    "test"
).install();

module.exports = function(Reservation) {
  function dateValidator(err) {
    if (this.startDate >= this.endDate) {
      err();
    }
  }

  function sendEmail(campground) {
    return new Promise((resolve, reject) => {
      Reservation.app.models.Email.send(formEmailObject(campground), 
        function(
          err,
          mail
        ) {
           if (err) {
            console.log(err);
            Raven.captureException(err);
            reject(err);
           } else {
             console.log(mail);
             console.log("email sent!");
             resolve(mail);
           }
       });
   });
} 

  function formEmailObject(campground) {
    return {
      to: "loopbackintern@yopmail.com",
      from: "noreply@optis.be",
      subject: "Thank you for your reservation at " + campground.name,
      html:
        "<p>We confirm your reservation for <strong>" +
        campground.name +
        "</strong></p>"
    };
   }

Reservation.validate("startDate", dateValidator, {
 message: "endDate should be after startDate"
});

Reservation.observe("after save", async function(ctx, next) {
 try {
  const campground = await Reservation.app.models.Campground.findById(
    ctx.instance.campgroundId
  );
  const mail = await sendEmail(campground);
  next();
 } catch (e) {
  Raven.captureException(e);
  next(e);
 }
});
};

Sorry for the poor formatting. When the flow is done I get this error:

(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.

I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.

I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.

Any idea? Thanks!

3 Answers3

3

if you are using async function you shouldn't explicitly call next, it gets automatically called.

check out this github issue for loopback async/await

so your hook can be like the following.

 Reservation.observe("after save", async ctx => {
  try {
    const campground = await Reservation.app.models.Campground.findById(
      ctx.instance.campgroundId
    );
    const mail = await sendEmail(campground);
  } catch (e) {
    Raven.captureException(e);
    throw e;
  }
});

NB: you don't need to wrap it in try catch unless you want to modify/work with the error.

mehari
  • 3,057
  • 2
  • 22
  • 34
  • Thank you Mehari! I didn't know that async function called next automatically. But I have one more question, is it right to call next with the error insteado of just throw it? Or is it innecessary too? – Jorge Belano Murphy May 28 '18 at 09:44
  • 1
    when you throw an error it will be passed to the next callback auto so you shouldn't call next at all. – mehari May 28 '18 at 10:36
  • Thanks again @Mehari! – Jorge Belano Murphy May 28 '18 at 10:57
  • @Mehari removing next() in async `before save` hook clear the warnings. Now the warnings are shown after `loaded` operation hook even if I've not implemented the `loaded` hook. What could have caused this? – 20B2 Jun 14 '18 at 10:03
  • @20B2 I can't say anything for sure from the context. I could help you if you can post some part of the code that might be causing the issue. you can contact me privately post a new question. – mehari Jun 14 '18 at 13:37
  • @Mehari I was having the warning whenever the `before save` hook gets called. Then I commented out the `next()` and the warning don't appear anymore. But when the end point to load resource gets called, the warning appear again which I came to know after adding `access` and `loaded` hooks to the `model.js` file. I don't know what is causing this error. All I know at the moment is that the warning started after `loaded` operation hook. – 20B2 Jun 14 '18 at 14:46
  • @20B2 make sure your callback is an async function. I am sorry but I don't understand your situation clearly, I am ready to help you if you can explain it well new post/question is a lot better with your code – mehari Jun 14 '18 at 15:04
0

You should declare your sendEmail method as async as it returns a promise.

async function sendEmail(campground) { ... }

  • [No need to do that](https://stackoverflow.com/questions/47880415/what-is-the-benefit-of-prepending-async-to-a-function-that-returns-a-promise). – Bergi May 28 '18 at 08:27
0

After reading this article, I created a await-handler.js file which include following code.

module.exports = (promise) =>
  promise
    .then(data => ({
        ok: true,
        data
    }))
    .catch(error =>
      Promise.resolve({
        ok: false,
        error
      })
    );

Then in MyModel.js file, I created a async function to get a value from database as follow.

const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
    let result = await awaitHandler(MyModel.find());
    if (result.ok) {
        if (result.data.length) {
            return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
        } else {
            return 0;
        }
    } else {
        return result.error;
    }
}

As per @Mehari's answer, I've commented call to next() method as follow:-

module.exports = function(MyModel) {
   MyModel.observe('before save', async(ctx, next) => {
       const maxNumber = await getMaxNumber (MyModel);
       if(ctx.instance) {
            ...
            set the required property using ctx.instance.* 
            like createdAt, createdBy properties
            ...
            // return next();
       } else {
            ...
            code for patch
            ...
            // return next();
       }
   })
}

This solves the warning issue whenever saving endpoint is triggered.

But the warning issue still appear when I run the endpoint to load the resource.Like http://localhost:3000/api/MyModel Previously, the issue appear only when the before save operation hook gets triggered.

After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.

MyModel.observe('access', (ctx, next) => {
    return next();
})

MyModel.observe('loaded', (ctx, next) => {
    return next();
})

What could have caused this issue and how can it gets resolved?

20B2
  • 2,011
  • 17
  • 30
  • @Mehari do you have any suggestions? I think I've explained what I've done till now clearly. – 20B2 Jun 14 '18 at 17:54