Feathers: How to cancel hook chain

Created on 1 Aug 2017  路  11Comments  路  Source: feathersjs/feathers

Steps to reproduce

I have this test before hook:

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
    const hookTest = function() {
      return function(hook) {
        return hook.app.service('myservice').find({
          query: {
            name: hook.data.name
          }
        })
        .then(result => {
          if (result.data.length === 1){
            return Promise.resolve(hook);//Works fine
          } else {
            throw 'PCU'; //throw an error. Record must exist
          }
        })
        .catch(error => {
            throw new errors.BadRequest(error, { datos: hook.data });
            //UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): BadRequest: PCU
        });
      };
    };
};

Expected behavior

Cancel hook chain without UnhandledPromiseRejectionWarning

Actual behavior

UnhandledPromiseRejectionWarning

System configuration

Module versions (especially the part that's not working):
feathers-cli version 2.2.1

NodeJS version:
v6.9.2

Most helpful comment

Closing since I think all questions should be answered. To skip a hook setting hook.cancelled and checking for iff(hook => !hook.cancelled, otherHooks), to skip the database call hook.result can be set in a before hook.

All 11 comments

Don't know if this helps but if you'd like to stop the hook chain without raising an error you can simply set the result like this: hook.result = xxx. However if you'd like to raise an error simply throw a Feathers error directly in your then statement:

if (xxx) {
  throw new errors.BadRequest('PCU')
}

Seems to be expected behaviour since you raise an error in your catch that is not catched at a higher level.

You could set hook.cancelled = true and then wrap everything that should into a iff hook checking iff(hook => !hook.cancelled, otherHooks)

@claustres @daffl Thank you. I'll try later.

According to https://docs.feathersjs.com/api/hooks.html#hook-functions, a hook can thow an error or rejects with an error. Should the documentation be changed?

@claustres Setting context.result prevents the DB call. It does not prevent the remaining before & after hooks from running (https://github.com/feathersjs/feathers-hooks-common/issues/236).

@claustres If "raise an error simply throw a Feathers error directly in your then statement", I get UnhandledPromiseRejectionWarning too

@daffl hook.cancelled = true and iff(hook => !hook.cancelled, otherHooks) works, but the operation is done in the database.

Would I need to mix both options? (setting context.result AND iff(hook => !hook.cancelled, otherHooks))

The problem is that in your catch you throw again and this error will not get catched by anyone if you don't have a higher catch block in your app, so the UnhandledPromiseRejectionWarning will eventually popup. For me the 2 throws are redondent. As far as I understand hooks can throw errors but you have to catch them at higher level if you don't want to have the UnhandledPromiseRejectionWarning.

What I can tell is that in my case I use a before hook to check for permissions. When the check fails on a create for instance I throw an error and set the result to null. Don't know which of both things does the job (or if it is actually a mix) but the object is not created in DB neither other hooks are ran.

@claustres Thank you. Next week I will do several tests and I will share the result

I have generated a new app using feathers-cli with 1 service and 1 hook. Code is here: https://github.com/kfern/feathers-issues/tree/feathers-issues-635

In generated hook source comments "Hooks can either return nothing or a promise" and return a promise. Generated hook test only check if the hook returns a Promise.

@daffl Can a hook only return a promise?

Yes a hook can return a promise only, this is the main behaviour for async hooks https://docs.feathersjs.com/api/hooks.html#asynchronous-hooks, if you don't have such async operations you can return nothing at all. In both cases what you do usually is simply to alter the hook object (typically hook.dataor hook.params).

A hook can return either a value (the new hook object) or a promise because promises consider values as resolved promises.

So return {} and return Promise.resolve({}) are eqivalent.

Closing since I think all questions should be answered. To skip a hook setting hook.cancelled and checking for iff(hook => !hook.cancelled, otherHooks), to skip the database call hook.result can be set in a before hook.

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue with a link to this issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings