0

How do I create a async-function timeout error handler as a hook in feathers that resides in the service file to handle promises in hooks?

Post created specifically as suggested by @Bergi on my previous question

If you are looking to implement a generic async-function timeout (for promises failing to fulfill for whatever reason) as a featherjs hook, you might want to ask a new (different!) question about specifically that.

I need a function that would be added to the database.hooks.js file (like the errorhandler in the example docs) that would handle exceptions (that cause timeouts) caused by hooks similar to get-database-by-id.js without changing the code in get-database-by-id.js:

get-database-by-id.js

const errors = require('@feathersjs/errors');

module.exports = function (options = {}) {
  return async context => {
    let promise = new Promise((resolve,reject) => { 
                    context.app.service('database').find({
                query: {"id":context.data.id}
                }).then(result => {

                      resolve(result.data[0].data)
                                           // console: error: Unhandled Rejection at: Promise 
                                           //browser:  hangs

            });
    });
    let result = await promise;
    if (result) {
        return context;
    }

  };
};

database.hooks.js (with example errorhandler from docs, does not work with promises)

const { authenticate } = require('@feathersjs/authentication').hooks;

const getDatabaseById = require('../../hooks/get-database-by-id');

const errors = require('@feathersjs/errors');

const errorHandler = ctx => {
  if (ctx.error) {
    const error = ctx.error;
    if (!error.code) {
      const newError = new errors.GeneralError("server error");
      ctx.error = newError;
      return ctx;
    }
    if (error.code === 404 || process.env.NODE_ENV === "production") {
      error.stack = null;
    }
    return ctx;
  }
};


module.exports = {
  before: {
    all: [ authenticate('jwt')],
    find: [],
    get: [],
    create: [
      getDatabaseById,
    ],
    update: [],
    patch: [],
    remove: []
  },

  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [errorHandler],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};
polar
  • 524
  • 5
  • 24
  • 1
    Not sure if leaving out the context is a good idea - the answers below seem to just come to the (correct) conclusion that you should avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)… – Bergi Dec 18 '19 at 01:13

2 Answers2

1

If featherjs find returns a promise, then there's no need to wrap it in one.

But I think I understand the question to mean that featherjs is broken in the sense that it doesn't reject or resolve on a missing id, and you'd like to force a resolution after a reasonable wait time. If I understand that correctly, you can implement your own timeout with Promise.race()

// find with the given id, or reject after quitAfter milliseconds
async function findById(id, quitAfter) {
  let timer = new Promise(function(resolve, reject) {
    setTimeout(reject, quitAfter, 'timeout exceeded');
  });
  let query = context.app.service('database').find({
    query: { "id": id }
  });
  return Promise.race([timer, query]);
}
danh
  • 62,181
  • 10
  • 95
  • 136
  • 1
    I don't think `find` is broken, it's just the OPs code where a rejection of the `find()` promise is not handled – Bergi Dec 18 '19 at 01:02
  • Ah, wouldn't OP see an unhandled promise rejection? I can delete this if it's not helpful – danh Dec 18 '19 at 01:03
  • @danh unhandled promise exceptions cause a hang in the browser, and only provide a rejection in the app console. Im trying to prevent the browser hang. – polar Dec 18 '19 at 01:08
  • @polar Not the unhandled rejection causes the hanging request, it's the non-resolving promise. – Bergi Dec 18 '19 at 01:08
  • @danh Yes, OP is seeing one, but please keep your answer - [I'm having a hard time figuring out what the OP actually wants](https://stackoverflow.com/q/59382435/1048572) – Bergi Dec 18 '19 at 01:09
  • Take a look at the errorhandler function example I provided, that's what I need help changing not the get-database-by-id.js code – polar Dec 18 '19 at 01:55
0

Your query function return a promise, then you do not need wrap it into a new Promise. And I hope you already install error hook, the hook will catch all errors which have been thrown from handler function.

In handle function, you just do your business, no need to take care exception errors.

module.exports = function (options = {}) {
  return async context => {
    const response = await context.app.service('database').find({
      query: { "id": context.data.id }
    });

    const result = response.data[0].data;
    if (result) {
      // Maybe you have to store `result` to context object,  like
      // context.result = result;
      return context;
    }
    return null;
  };
};

In your case, maybe you will get back and error on your client, and you can guest the reason why your code did not work.

hoangdv
  • 15,138
  • 4
  • 27
  • 48
  • This is a modification to the hook itself, I need help with the errorhandler runction in the other file. See the example above. – polar Dec 18 '19 at 01:57
  • @polar The error handler is not, and cannot be, called when the hook is broken so that instead of properly rejecting the returned promise with the error it does nothing. You *need* to fix the hook. There's no way around this. – Bergi Dec 18 '19 at 02:24
  • @hoangdv While the browser no longer hangs, this still only shows the error in the app log and allows the operation to complete with the error and faulty data, it does not stop the operation and present the browser/client with the error – polar Dec 21 '19 at 03:59