Search code examples
node.jsloopbackjs

loopback - "next()" not firing at the correct time in "beforeRemote"?


I am trying to update the database from a remote source prior to the find method returning results. I am trying to use the "beforeRemote" method on the "find" action. It is updating the database, but it is not waiting until the database update is done before returning the data. I can tell because the first time I call the "find" endpoint against an empty database, the result is empty, but when checking the database following the call, it has the data in it.

Here is my model class (sensitive stuff removed).

'use strict';
var Cronofy = require('cronofy');
var _ = require('lodash');

module.exports = function (Event) {

  // remote method before hook
  Event.beforeRemote('find', function (ctx, unused, next) {
    var client = new Cronofy({
      access_token: 'secret-token',
    });

    var options = {
      from: "2018-10-15",
      to: "2018-11-15",
      tzid: 'Etc/UTC'
    };


    client.readEvents(options)
      .then(function (response) {
        var returnedEvents = response.events;
        var events = _.filter(returnedEvents, function(o){
          return !_.isEmpty(o.summary) && !_.isEmpty(o.event_uid) && !_.isEmpty(o.start) && !_.isEmpty(o.end);
        });

        events.forEach(element => {
          Event.upsertWithWhere({
              sourceType: "external-source-a",
              sourceID: element.event_uid
            }, {
              sourceType: "external-source-a",
              sourceID: element.event_uid,
              summary: element.summary,
              description: element.description,
              start: element.start,
              end: element.end,
              recurring: element.recurring
            },
            function (err, model) {
              if (err) {
                console.log(err);
              }
              //console.log(model);
            }
          );
        });
        next();
      }).catch(console.log);
  });
};

I'm new to loopback so I'm sure it's a simple mistake. What have I done wrong?


Solution

  • use Promise.all Promise.all

    You shouldn't use forEach with asynchronous operation in your case the update because forEach will not await until the update is finished and then call next. Instead use map for example to loop on the events and return a promise of the update operation so you can create array of promises and use Promise.all. In the then of the Promise.all call the next method.

        module.exports = function (Event) {
    
        // remote method before hook
        Event.beforeRemote('find', function (ctx, unused, next) {
          var client = new Cronofy({
            access_token: 'secret-token',
          });
    
          var options = {
            from: "2018-10-15",
            to: "2018-11-15",
            tzid: 'Etc/UTC'
          };
    
          client.readEvents(options)
            .then(function (response) {
              var returnedEvents = response.events;
              var events = _.filter(returnedEvents, function(o){
                return !_.isEmpty(o.summary) && !_.isEmpty(o.event_uid) && !_.isEmpty(o.start) && !_.isEmpty(o.end);
              });
              const updatepromises = events.map((element) => {
                return Event.upsertWithWhere({
                  sourceType: "external-source-a",
                  sourceID: element.event_uid
                }, {
                    sourceType: "external-source-a",
                    sourceID: element.event_uid,
                    summary: element.summary,
                    description: element.description,
                    start: element.start,
                    end: element.end,
                    recurring: element.recurring
                  });
              }); 
              return Promise.all(updatepromises); 
    
            })
            .then((result) => next())
            .catch(console.log);
        });
      };
    

    Or you can use async/await async/await which is more readable and you do no have to call next loopback will take care of that for you.

        module.exports = function (Event) {
      // remote method before hook
      Event.beforeRemote('find', async function (ctx, unused) {
        var client = new Cronofy({
          access_token: 'secret-token',
        });
        var options = {
          from: "2018-10-15",
          to: "2018-11-15",
          tzid: 'Etc/UTC'
        };
        var response = await client.readEvents(options);
        var returnedEvents = response.events;
        var events = _.filter(returnedEvents, function (o) {
          return !_.isEmpty(o.summary) && !_.isEmpty(o.event_uid) && !_.isEmpty(o.start) && !_.isEmpty(o.end);
        });
        const updatepromises = events.map((element) => {
          return Event.upsertWithWhere({
            sourceType: "external-source-a",
            sourceID: element.event_uid
          }, {
              sourceType: "external-source-a",
              sourceID: element.event_uid,
              summary: element.summary,
              description: element.description,
              start: element.start,
              end: element.end,
              recurring: element.recurring
            });
        });
        await Promise.all(updatepromises);
      });
    };