hooks

2019-08-14 admin

hooks是什么

什么是hooks,Adds pre and post hook functionality to your JavaScript methods.

hooks使用教程帮助文档

hooks

Add pre and post middleware hooks to your JavaScript methods.

Installation

npm install hooks

Motivation

Suppose you have a JavaScript object with a save method.

It would be nice to be able to declare code that runs before save and after save. For example, you might want to run validation code before every save, and you might want to dispatch a job to a background job queue after save.

One might have an urge to hard code this all into save, but that turns out to couple all these pieces of functionality (validation, save, and job creation) more tightly than is necessary. For example, what if someone does not want to do background job creation after the logical save?

It is nicer to tack on functionality using what we call pre and post hooks. These are functions that you define and that you direct to execute before or after particular methods.

Example

We can use hooks to add validation and background jobs in the following way:

var hooks = require('hooks')
  , Document = require('./path/to/some/document/constructor');

// Add hooks' methods: `hook`, `pre`, and `post`
for (var k in hooks) {
  Document[k] = hooks[k];
}

// Define a new method that is able to invoke pre and post middleware
Document.hook('save', Document.prototype.save);

// Define a middleware function to be invoked before 'save'
Document.pre('save', function validate (next) {
  // The `this` context inside of `pre` and `post` functions
  // is the Document instance
  if (this.isValid()) next();      // next() passes control to the next middleware
                                   // or to the target method itself
  else next(new Error("Invalid")); // next(error) invokes an error callback
});

// Define a middleware function to be invoked after 'save'
Document.post('save', function createJob (next) {
  this.sendToBackgroundQueue();
  next();
});

If you already have defined Document.prototype methods for which you want pres and posts, then you do not need to explicitly invoke Document.hook(...). Invoking Document.pre(methodName, fn) or Document.post(methodName, fn) will automatically and lazily change Document.prototype[methodName] so that it plays well with hooks. An equivalent way to implement the previous example is:

var hooks = require('hooks')
  , Document = require('./path/to/some/document/constructor');

// Add hooks' methods: `hook`, `pre`, and `post`
for (var k in hooks) {
  Document[k] = hooks[k];
}

Document.prototype.save = function () {
  // ...
};

// Define a middleware function to be invoked before 'save'
Document.pre('save', function validate (next) {
  // The `this` context inside of `pre` and `post` functions
  // is the Document instance
  if (this.isValid()) next();      // next() passes control to the next middleware
                                   // or to the target method itself
  else next(new Error("Invalid")); // next(error) invokes an error callback
});

// Define a middleware function to be invoked after 'save'
Document.post('save', function createJob (next) {
  this.sendToBackgroundQueue();
  next();
});

Pres and Posts as Middleware

We structure pres and posts as middleware to give you maximum flexibility:

  1. You can define multiple pres (or posts) for a single method.
  2. These pres (or posts) are then executed as a chain of methods.
  3. Any functions in this middleware chain can choose to halt the chain’s execution by nexting an Error from that middleware function. If this occurs, then none of the other middleware in the chain will execute, and the main method (e.g., save) will not execute. This is nice, for example, when we don’t want a document to save if it is invalid.

Defining multiple pres (or posts)

pre and post are chainable, so you can define multiple via:

Document.pre('save', function (next) {
  console.log("hello");
  next();
}).pre('save', function (next) {
  console.log("world");
  next();
});

Document.post('save', function (next) {
  console.log("hello");
  next();
}).post('save', function (next) {
  console.log("world");
  next();
});

As soon as one pre finishes executing, the next one will be invoked, and so on.

Error Handling

You can define a default error handler by passing a 2nd function as the 3rd argument to hook:

Document.hook('set', function (path, val) {
  this[path] = val;
}, function (err) {
  // Handler the error here
  console.error(err);
});

Then, we can pass errors to this handler from a pre or post middleware function:

Document.pre('set', function (next, path, val) {
  next(new Error());
});

If you do not set up a default handler, then hooks makes the default handler that just throws the Error.

The default error handler can be over-rided on a per method invocation basis.

If the main method that you are surrounding with pre and post middleware expects its last argument to be a function with callback signature function (error, ...), then that callback becomes the error handler, over-riding the default error handler you may have set up.

Document.hook('save', function (callback) {
  // Save logic goes here
  ...
});

var doc = new Document();
doc.save( function (err, saved) {
  // We can pass err via `next` in any of our pre or post middleware functions
  if (err) console.error(err);

  // Rest of callback logic follows ...
});

Mutating Arguments via Middleware

pre and post middleware can also accept the intended arguments for the method they augment. This is useful if you want to mutate the arguments before passing them along to the next middleware and eventually pass a mutated arguments list to the main method itself.

As a simple example, let’s define a method set that just sets a key, value pair. If we want to namespace the key, we can do so by adding a pre middleware hook that runs before set, alters the arguments by namespacing the key argument, and passes them onto set:

Document.hook('set', function (key, val) {
  this[key] = val;
});
Document.pre('set', function (next, key, val) {
  next('namespace-' + key, val);
});
var doc = new Document();
doc.set('hello', 'world');
console.log(doc.hello); // undefined
console.log(doc['namespace-hello']); // 'world'

As you can see above, we pass arguments via next.

If you are not mutating the arguments, then you can pass zero arguments to next, and the next middleware function will still have access to the arguments.

Document.hook('set', function (key, val) {
  this[key] = val;
});
Document.pre('set', function (next, key, val) {
  // I have access to key and val here
  next(); // We don't need to pass anything to next
});
Document.pre('set', function (next, key, val) {
  // And I still have access to the original key and val here
  next();
});

Finally, you can add arguments that downstream middleware can also see:

// Note that in the definition of `set`, there is no 3rd argument, options
Document.hook('set', function (key, val) {
  // But...
  var options = arguments[2]; // ...I have access to an options argument
                              // because of pre function pre2 (defined below)
  console.log(options); // '{debug: true}'
  this[key] = val;
});
Document.pre('set', function pre1 (next, key, val) {
  // I only have access to key and val arguments
  console.log(arguments.length); // 3
  next(key, val, {debug: true});
});
Document.pre('set', function pre2 (next, key, val, options) {
  console.log(arguments.length); // 4
  console.log(options); // '{ debug: true}'
  next();
});
Document.pre('set', function pre3 (next, key, val, options) {
  // I still have access to key, val, AND the options argument introduced via the preceding middleware
  console.log(arguments.length); // 4
  console.log(options); // '{ debug: true}'
  next();
});

var doc = new Document()
doc.set('hey', 'there');

Post middleware

Post middleware intercepts the callback originally sent to the asynchronous function you have hooked to.

This means that the following chain of execution will occur in a typical save operation:

(1) doc.save -> (2) pre --(next)–> (3) save calls back -> (4) post --(next)–> (5) targetFn

Illustrated below:

Document.pre('save', function (next) {
  this.key = "value";
  next();
});
// Post handler occurs before `set` calls back. This is useful if we need to grab something
// async before `set` finishes.
Document.post('set', function (next) {
  var me = this;
  getSomethingAsync(function(value){ // let's assume it returns "Hello Async"
    me.key2 = value;
    next();
  });
});

var doc = new Document();
doc.save(function(err){
  console.log(this.key);  // "value" - this value was saved
  console.log(this.key2); // "Hello Async" - this value was *not* saved
}

Post middleware must call next() or execution will stop.

Parallel pre middleware

All middleware up to this point has been “serial” middleware – i.e., middleware whose logic is executed as a serial chain.

Some scenarios call for parallel middleware – i.e., middleware that can wait for several asynchronous services at once to respond.

For instance, you may only want to save a Document only after you have checked that the Document is valid according to two different remote services.

We accomplish asynchronous middleware by adding a second kind of flow control callback (the only flow control callback so far has been next), called done.

  • next passes control to the next middleware in the chain
  • done keeps track of how many parallel middleware have invoked done and passes control to the target method when ALL parallel middleware have invoked done. If you pass an Error to done, then the error is handled, and the main method that is wrapped by pres and posts will not get invoked.

We declare pre middleware that is parallel by passing a 3rd boolean argument to our pre definition method.

We illustrate via the parallel validation example mentioned above:

Document.hook('save', function targetFn (callback) {
  // Save logic goes here
  // ...
  // This only gets run once the two `done`s are both invoked via preOne and preTwo.
});

                     // true marks this as parallel middleware
Document.pre('save', true, function preOne (next, doneOne, callback) {
  remoteServiceOne.validate(this.serialize(), function (err, isValid) {
    // The code in here will probably be run after the `next` below this block
    // and could possibly be run after the console.log("Hola") in `preTwo
    if (err) return doneOne(err);
    if (isValid) doneOne();
  });
  next(); // Pass control to the next middleware
});

// We will suppose that we need 2 different remote services to validate our document
Document.pre('save', true, function preTwo (next, doneTwo, callback) {
  remoteServiceTwo.validate(this.serialize(), function (err, isValid) {
    if (err) return doneTwo(err);
    if (isValid) doneTwo();
  });
  next();
});

// While preOne and preTwo are parallel, preThree is a serial pre middleware
Document.pre('save', function preThree (next, callback) {
  next();
});

var doc = new Document();
doc.save( function (err, doc) {
  // Do stuff with the saved doc here...
});

In the above example, flow control may happen in the following way:

(1) doc.save -> (2) preOne --(next)–> (3) preTwo --(next)–> (4) preThree --(next)–> (wait for dones to invoke) -> (5) doneTwo -> (6) doneOne -> (7) targetFn

So what’s happening is that:

  1. You call doc.save(...)
  2. First, your preOne middleware gets executed. It makes a remote call to the validation service and next()s to the preTwo middleware.
  3. Now, your preTwo middleware gets executed. It makes a remote call to another validation service and next()s to the preThree middleware.
  4. Your preThree middleware gets executed. It immediately next()s. But nothing else gets executing until both doneOne and doneTwo are invoked inside the callbacks handling the response from the two valiation services.
  5. We will suppose that validation remoteServiceTwo returns a response to us first. In this case, we call doneTwo inside the callback to remoteServiceTwo.
  6. Some fractions of a second later, remoteServiceOne returns a response to us. In this case, we call doneOne inside the callback to remoteServiceOne.
  7. hooks implementation keeps track of how many parallel middleware has been defined per target function. It detects that both asynchronous pre middlewares (preOne and preTwo) have finally called their done functions (doneOne and doneTwo), so the implementation finally invokes our targetFn (i.e., our core save business logic).

Removing Pres

You can remove a particular pre associated with a hook:

Document.pre('set', someFn);
Document.removePre('set', someFn);

And you can also remove all pres associated with a hook: Document.removePre(‘set’); // Removes all declared pres on the hook ‘set’

Tests

To run the tests: make test

Contributors

License

MIT License


Author

Brian Noguchi

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-72491.html

文章标题:hooks

相关文章
Understanding Vue.js Lifecycle Hooks
Lifecycle hooks are an important part of any serious component. You often need to know when your component is created, a...
2018-03-01
可能是基于 Hooks 和 Typescript 最好的状态管理工具
接上一篇:我理想中的状态管理工具 之前说,对于我个人来而言,理想的状态管理工具只需同时满足两个特点: 简单易用,并且适合中大型项目 完美地支持 Typescript 未能找到一个完美满足这两点的,所以我决定自己造了一个:叫 Stamen...
2018-11-14
使用React-Hooks开发聊天室之2.0版本
React 技术 我的 React 历程 React 在前端界大行其道将近三年了,他带来的数据与 UI 绑定的优势,让我们告别了 jQuery 和 DOM,让我们把注意力集中到单向数据流上,我们可以把一大个 web app 拆分成小的,独...
2018-12-13
react-hooks中的一些懵逼点
前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把 Hooks的官方教程过了一遍,收获颇多,惊叹这个新特性真 TM 好用,以后开发用这个怕是要起飞了😆。 状态钩子(State Hook) const [state, ...
2019-02-22
理解 React Hooks
上周,Sophie Alpert 和 Dan Abramov 在 React Conf 2018 中 提出了 hooks 这个概念,让我们一起来看看 Hooks 在解决一个什么问题。 TL;DR 一句话总结 React Hooks 就是...
2018-11-04
(译)React hooks:它不是一种魔法,只是一个数组——使用图表揭秘提案规则
原文地址:https://medium.com/@ryardley/… 译文:染陌 (Github) 译文地址:https://github.com/answershuto/Blog 转载请著名出处 我是一名hooks API的忠实粉丝,然...
2018-11-26
可复用的 React Hooks Library
A set of reusable React Hooks. Hooks are a new feature proposal that lets you use state and other React features withou...
2018-10-26
react hooks 学习笔记
这篇为react hooks的学习笔记,仅对需要关注的重点做了记录。有关react hooks详细说明,参见官网https://reactjs.org/docs/hook… Rules of Hooks 不能将 hooks 放在循环、条...
2019-01-09
How can I make Mocha load a helper.js file that defines global hooks or utilities?
LouisZlatko提出了一个问题:How can I make Mocha load a helper.js file that defines global hooks or utilities?,或许与您遇到的问题类似。 回答者Co...
2018-04-27
React Hooks 从入门到上手
前言 楼主最近在整理 React Hooks 的一些资料,为项目重构作准备,下午整理成了这篇文章。 如果之前没接触过相关概念,那么通过这篇文章, 你将会了什么是React Hooks , 它是做什么的 , 以及如何使用。 下面我会用一个...
2019-03-16
回到顶部