Lua 闭包(Closure)
2020-09-07 本文已影响0人
香菜香菜我是折耳根
今天看到一个非常生动的使用闭包概念的代码片段,决定记录下来,这个代码出自 Kong 的插件 rate-limiting :
function RateLimitingHandler:access(conf)
-- ...
kong.ctx.plugin.timer = function()
local ok, err = timer_at(0, increment, conf, limits, identifier, current_timestamp, 1)
if not ok then
kong.log.err("failed to create timer: ", err)
end
end
end
function RateLimitingHandler:log(_)
if kong.ctx.plugin.timer then
kong.ctx.plugin.timer()
end
end
在学习闭包概念的时候,对里面提到的一句话不是非常理解:闭包可以捕捉作用域内的变量。一直没有找到特别好的实际用例,在这个代码片段里就能比较清晰的理解了。 access 阶段声明了一个延时函数的闭包,这个闭包在 log 阶段进行调用。如果不用闭包的话,代码会是什么样子呢?
function RateLimitingHandler:access(conf)
-- ...
kong.ctx.plugin.limits = limits
kong.ctx.plugin.identifier = identifier
kong.ctx.plugin.timestamp = current_timestamp
end
function RateLimitingHandler:log(_)
local limits = kong.ctx.plugin.limits
local identifier = kong.ctx.plugin.identifier
local current_timestamp = kong.ctx.plugin.timestamp
local ok, err = timer_at(0, increment, conf, limits, identifier, current_timestamp, 1)
if not ok then
kong.log.err("failed to create timer: ", err)
end
end
我们需要使用 kong.ctx.plugin
来在 access 和 log 阶段之间共享变量,这样代码会显得很臃肿,如果使用闭包进行作用域变量的捕捉,那么代码就会写得简洁优雅。
如何修改闭包内的变量?这里我依然引用 Kong 2.x 的插件代码,比方说我们要开发一个插件的 Admin API ,我们需要在插件目录下创建 api.lua :
local endpoints = require "kong.api.endpoints"
local kong = kong
local caches_schema = kong.db.caches.schema
return {
["/caches"] = {
schema = caches_schema,
methods = {
GET = endpoints.get_collection_endpoint(caches_schema),
},
},
["/caches/:cache_id"] = {
schema = caches_schema,
methods = {
GET = function(self, ...)
self.params.devices = { id = self.params.id }
return endpoints.get_entity_endpoint(caches_schema)(self, ...)
end,
},
},
}
首先看下这里的 get_collection_endpoint
和 get_entity_endpoint
方法是什么东西( kong/api/endpoints.lua
):
local function get_entity_endpoint(schema, foreign_schema, foreign_field_name, method, is_foreign_entity_endpoint)
return function(self, db, helpers)
-- ...
end
end
可以看到,这里其实是返回了一个闭包,那么我们该如何修改这个闭包的参数呢?就是创建一个外部的函数,在这个函数里调用这个闭包,从而修改:
GET = function(self, ...)
self.params.caches = { id = self.params.id }
return endpoints.get_entity_endpoint(caches_schema)(self, ...)
end,