钩子

钩子是可插入的中间件函数,可以在 before, after 或者在a 服务 的 __error__s 之前注册. 您可以注册单个钩子函数或创建它们的链,以创建复杂的工作流程. 大多数情况下,多个挂钩都被注册,因此示例显示了“挂钩链”数组样式注册.

钩子是 独立传输,这意味着它是否通过HTTP(S)(REST),Socket.io,Primus或任何其他传输Feathers可能在将来支持无关紧要.它们也是服务不可知的,这意味着它们可以与任何服务一起使用,无论它们是否具有模型.

钩子通常用于处理诸如验证,记录,填充相关实体,发送通知等内容.此模式使您的应用程序逻辑保持灵活,可组合,并且更容易跟踪和调试.有关钩子背后的设计模式的更多信息,请参阅 此博客文章.

快速示例

下面的示例在将数据保存到数据库之前添加了 createdAtupdatedAt 属性,并记录了服务上的任何错误:

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

const app = feathers();

app.service('messages').hooks({
  before: {
    create(context) {
      context.data.createdAt = new Date();
    },

    update(context) {
      context.data.updatedAt = new Date();
    },

    patch(context) {
      context.data.updatedAt = new Date();
    }
  },

  error(context) {
    console.error(`Error in ${context.path} calling ${context.method} method`, context.error);
  }
});

钩子功能

钩子函数可以是普通的或 async 函数或箭头函数,它以 hook context 作为参数,可以

  • return a context object

  • return nothing (undefined)

  • 返回 feathers.SKIP 以跳过所有进一步的钩子

  • throw 错误

  • 对于异步操作,返回一个 Promise

    • context 对象解析

    • undefined 解决

    • 拒绝错误

有关更多信息,请参阅 hook flowasynchronous hooks section.

// normal hook function
function(context) {
  return context;
}

// asynchronous hook function with promise
function(context) {
  return Promise.resolve(context);
}

// async hook function
async function(context) {
  return context;
}

// normal arrow function
context => {
  return context;
}

// asynchronous arrow function with promise
context => {
  return Promise.resolve(context);
}

// async arrow function
async context => {
  return context;
}

// skip further hooks
const feathers = require('@feathersjs/feathers');

async context => {
  return feathers.SKIP;
}

钩子上下文

钩子 context 被传递给一个钩子函数,并包含有关服务方法调用的信息.它具有 只读 属性,不应被修改, 可写 属性,可以为后续挂钩更改.

小技巧

在整个服务方法调用中,``context`` 对象是相同的,因此可以添加属性并在以后的其他钩子中使用它们.

context.app

应用. 这可以用于检索其他服务(通过 context.app.service('name'))或配置值.

context.service

context.service 是一个 read only 属性,包含当前运行此钩子的服务.

context.path

context.path 是一个 read only 属性,包含没有前导或尾部斜杠的服务名称(或路径).

context.method

服务 (one of find, get, create, update, patch, remove).

context.type

context.type 是一个 read only 属性,带有钩子类型(before, aftererror 之一).

context.params

context.params is a writeable property that contains the 服务 parameters (including params.query). For more information see the params.

context.id

context.id is a writeable property and the id for a get, remove, update and patch service method call. For remove, update and patch context.id can also be null when modifying multiple entries. In all other cases it will be undefined.

注解

context.id is only available for method types get, remove, update and patch.

context.data

context.data is a writeable property containing the data of a create, update and patch service method call.

注解

context.data``只适用于方法类型``create,``update``和``patch``.

context.error

context.error is a writeable property with the error object that was thrown in a failed method call. It is only available in error hooks.

注解

context.error will only be available if context.type is error.

context.result

context.result is a writeable property containing the result of the successful service method call. It is only available in after hooks. context.result can also be set in

  • A before hook to skip the actual service method (database) call

  • An error hook to swallow the error and return a result instead

注解

context.result will only be available if context.type is after or if context.result has been set.

context.dispatch

context.dispatch is a writeable, optional property and contains a “safe” version of the data that should be sent to any client. If context.dispatch has not been set context.result will be sent to the client instead.

注解

context.dispatch only affects the data sent through a Feathers Transport like REST or Socket.io. An internal method call will still get the data set in context.result.

context.statusCode

context.statusCode is a writeable, optional property that allows to override the standard HTTP status code that should be returned.

钩子流

In general, hooks are executed in the order they are registered with the original service method being called after all before hooks. This flow can be affected as follows.

抛出一个错误

When an error is thrown (or the promise is rejected), all subsequent hooks - and the service method call if it didn’t run already - will be skipped and only the error hooks will run.

The following example throws an error when the text for creating a new message is empty. You can also create very similar hooks to use your Node validation library of choice.

app.service('messages').hooks({
  before: {
    create: [
      function(context) {
        if(context.data.text.trim() === '') {
          throw new Error('Message text can not be empty');
        }
      }
    ]
  }
});

Setting context.result

When context.result is set in a before hook, the original 服务 call will be skipped. All other hooks will still execute in their normal order. The following example always returns the currently authenticated user instead of the actual user for all get method calls:

app.service('users').hooks({
  before: {
    get: [
      function(context) {
        // Never call the actual users service
        // just use the authenticated user
        context.result = context.params.user;
      }
    ]
  }
});

Returning feathers.SKIP

require('@feathersjs/feathers').SKIP can be returned from a hook to indicate that all following hooks should be skipped. If returned by a before hook, the remaining before hooks are skipped; any after hooks will still be run. If it hasn’t run yet, the service method will still be called unless context.result is set already.

异步钩子

When the hook function is async or a Promise is returned it will wait until all asynchronous operations resolve or reject before continuing to the next hook.

重要

As stated in the hook functions section the promise has to either resolve with the context object (usually done with .then(() => context) at the end of the promise chain) or with undefined.

async/await

When using Node v8.0.0 or later the use of async/await is highly recommended. This will avoid many common issues when using Promises and asynchronous hook flows. Any hook function can be async in which case it will wait until all await operations are completed. Just like a normal hook it should return the context object or undefined.

The following example shows an async/await hook that uses another service to retrieve and populate the messages user when getting a single message:

app.service('messages').hooks({
  after: {
    get: [
      async function(context) {
        const userId = context.result.userId;

        // Since context.app.service('users').get returns a promise we can `await` it
        const user = await context.app.service('users').get(userId);

        // Update the result (the message)
        context.result.user = user;

        // Returning will resolve the promise with the `context` object
        return context;
      }
    ]
  }
});

回报承诺

The following example shows an asynchronous hook that uses another service to retrieve and populate the messages user when getting a single message.

app.service('messages').hooks({
  after: {
    get: [
      function(context) {
        const userId = context.result.userId;

        // context.app.service('users').get returns a Promise already
        return context.app.service('users').get(userId).then(user => {
          // Update the result (the message)
          context.result.user = user;

          // Returning will resolve the promise with the `context` object
          return context;
        });
      }
    ]
  }
});

注解

A common issue when hooks are not running in the expected order is a missing return statement of a promise at the top level of the hook function.

重要

Most Feathers service calls and newer Node packages already return Promises. They can be returned and chained directly. There is no need to instantiate your own new Promise instance in those cases.

转换回调

When the asynchronous operation is using a callback instead of returning a promise you have to create and return a new Promise (new Promise((resolve, reject) => {})) or use util.promisify.

The following example reads a JSON file converting fs.readFile with util.promisify:

const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);

app.service('messages').hooks({
  after: {
    get: [
      function(context) {
        return readFile('./myfile.json').then(data => {
          context.result.myFile = data.toString();

          return context;
        });
      }
    ]
  }
});

小技巧

Other tools like Bluebird also help converting between callbacks and promises.

注册钩子

Hook函数通过``app.service(<servicename>).hooks(hooks)``方法在服务上注册.什么可以作为``hooks``传递:

// The standard all at once way (also used by the generator)
// an array of functions per service method name (and for `all` methods)
app.service('servicename').hooks({
  before: {
    all: [
      // Use normal functions
      function(context) { console.log('before all hook ran'); }
    ],
    find: [
      // Use ES6 arrow functions
      context => console.log('before find hook 1 ran'),
      context => console.log('before find hook 2 ran')
    ],
    get: [ /* other hook functions here */ ],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
});

// Register a single hook before, after and on error for all methods
app.service('servicename').hooks({
  before(context) {
    console.log('before all hook ran');
  },
  after(context) {
    console.log('after all hook ran');
  },
  error(context) {
    console.log('error all hook ran');
  }
});

小技巧

当使用完整对象时, all 是一个特殊的关键字, 这意味着这个钩子将为所有方法运行. all 钩子将在其他特定于方法的钩子之前注册.

小技巧

app.service(<servicename>).hooks(hooks) can be called multiple times and the hooks will be registered in that order. Normally all hooks should be registered at once however to see at a glance what the service is going to do.

应用程序挂钩

To add hooks to every service app.hooks(hooks) can be used. Application hooks are registered in the same format as service hooks and also work exactly the same.

注解

when application hooks will be executed however:

  • before 应用程序挂钩总是在*所有服务 before 挂钩之前运行*

  • after 应用程序挂钩总是在 all service ``after`` 挂钩后运行

  • error 应用程序挂钩总是在 all service ``error`` 挂钩后运行

下面是一个非常有用的应用程序挂钩的示例, 它使用服务和方法名称以及错误堆栈记录每个服务方法错误.

app.hooks({
  error(context) {
    console.error(`Error in '${context.path}' service method '${context.method}'`, context.error.stack);
  }
});