问题总结(Problem Summary)
You are using a npm module with supported functions that have callbacks as parameters. You want that every time you handle a callback, you add some tasks before the actual callback, such as logging “callback xyz is about to run”. In this article, I will show you how to do that by modifying the prototype of the module’s API functions. This concept is known as intercepting. It’s pretty similar to the concept “middleware” in HTTP server request handling.
您正在使用npm模块,其支持的功能将回调作为参数。 您希望每次处理回调时,都在实际回调之前添加一些任务,例如记录“回调xyz将要运行”。 在本文中,我将向您展示如何通过修改模块的API函数的原型来做到这一点。 这个概念称为拦截。 它与HTTP服务器请求处理中的“中间件”概念非常相似。
示例场景 (Sample scenario)
My project was using the npm module “request” and I wanted to intercept the response callback for every request called from this module. Unfortunately, the module doesn’t support this feature. I couldn’t choose another module that supports it (such as “axios”) for historical reasons. To understand more about the problem, please look at this code:
我的项目正在使用npm模块“ request”,我想拦截从该模块调用的每个请求的响应回调。 不幸的是,该模块不支持此功能。 由于历史原因,我无法选择支持它的另一个模块(例如“ axios”)。 要了解有关该问题的更多信息,请查看以下代码:
const request = require('request');
const myMiddleware = require('./middleware');request('https://google.com', (err, res, body) => {
myMiddleware(); // I need to intercept this function to every response handling, i.e. callback
if (err) {
console.log(err);
return;
}
// do something with the response
});
For every function call like this, I want to integrate a middleware to it so it can do some tasks like logging the status code, verify the response data. I want that the myMiddleware() is automatically called in every response handling (callback), instead of repeating this function call. Therefore, I had to “cheat” the API functions of the module so it can behave differently as I wanted.
对于这样的每个函数调用,我都希望将一个中间件集成到该中间件,以便它可以执行某些任务,例如记录状态代码,验证响应数据。 我希望在每个响应处理(回调)中自动调用myMiddleware(),而不是重复此函数调用。 因此,我不得不“欺骗”模块的API函数,以使其表现出与所需不同的效果。
解 (Solution)
Please note that this solution is very tricky, it’s is to modifying the original module functions. So use it at your own risk.
请注意,此解决方案非常棘手,它是修改原始模块功能。 因此,使用它需要您自担风险。
Before jumping in the solution, let’s me remind you how a callback by looking at this code:
在进入解决方案之前,让我通过查看以下代码来提醒您如何进行回调:
const doSomething = (param1, callback) => {
// the original function handling that lead to some results
const result = param1;
callback(result);
};doSomething(1, (input) => {
console.log('callback called, input is', input);
});// 'callback called, input is 1' is logged out
So I defined a function doSomething();, it does some specific tasks and when everything done, it passes result(s) to the callback so the following tasks can be different in different function calls doSomething();. As you can see the callback is always called at the end of the original function handling, when all the original tasks are done.
因此,我定义了一个函数doSomething();,它执行一些特定的任务,并且在完成所有操作后,将结果传递给回调,以便在不同的函数调用doSomething();中以下任务可以有所不同。 如您所见,当所有原始任务完成后,回调总是在原始函数处理结束时调用。
My solution is to add my custom tasks before the callback call. Have a look at this code:
我的解决方案是在回调调用之前添加我的自定义任务。 看一下这段代码:
const request = requires('request')
const myMiddleware = require('./middleware');// intercept the callback
const _original = request
request = (a1, a2, a3) => {
const _callback = a2
a2 = (err, res, body) => {
myMiddleware() // <--------------
_callback(err, res, body)
}
_original(a1, a2, a3)
}// keep all the properties/prototypes of the module export
Object.keys(_original).forEach(key => {
request[key] = _original[key]
})request('https://google.com', (err, res, body) => {
if (err) {
console.log(err);
return;
}
// do something with the response
});
The original request(); treats the second parameter (a2) as the callback. The callback function has three parameters (err, res, body) input by the original tasks. I assign the original callback as _callback then replace the actual callback with myMiddleware(), then _callback(), so the original callback is still called but following to my interceptor (myMiddleware()). After that, I replace the original module function (_original) with the one that has the customized callback then use it instead. So that’s how you intercept a callback function using characteristics of JaveScript!
原始request(); 将第二个参数(a2)视为回调。 回调函数具有由原始任务输入的三个参数(err,res,body) 。 我将原始回调分配为_callback,然后用myMiddleware()和_callback()替换实际的回调,因此仍调用原始回调,但随后是我的拦截器( myMiddleware() ) 。 之后,我将原始模块函数(_original)替换为具有自定义回调的函数,然后改用它。 这样便可以利用JaveScript的特性来拦截回调函数!
For this specific case, the variable const request = require(‘request’) has other properties/prototypes such as request.get(), request.post(). So you have to reassign the customized request’s properties/prototypes to the original ones, using Object.keys.forEach().
对于这种特定情况,变量const request = require('request')具有其他属性/原型,例如request.get(),request.post() 。 因此,您必须使用Object.keys.forEach()将自定义请求的属性/原型重新分配给原始属性/原型。
额外 (Extra)
If you want to apply this to your entire project, meaning that every time you require the module ‘request’ in a certain file, the interceptor is added, you can use the module npm ‘intercept-require’ like this:
如果要将其应用于整个项目,这意味着每次在某个文件中需要模块“请求”时,都会添加拦截器,您可以使用模块npm “ intercept-require”,如下所示:
const moduleInterceptor = require('intercept-require');moduleInterceptor((module, info) => {
if (info.moduleId !== 'request') { return module; }
// intercept the callback return module;
})const request = require('request');
// using request
闭幕 (Closing)
Hope this article can help you to understand more about JavaScript and save some time to handle or workaround some tricky issues.
希望本文可以帮助您了解有关JavaScript的更多信息,并节省一些时间来处理或解决一些棘手的问题。