# #go
Вопрос:
Я изучаю исходный код golang и застреваю в порядке выполнения функции отсрочки. У меня есть два файла: один определяет поведение конечной точки, а другой-для теста. Я удаляю некоторый код, не связанный с моим вопросом, чтобы уменьшить количество строк для чтения. Файл определения конечной точки
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
// Chain is a helper function for composing middlewares. Requests will
// traverse them in the order they're declared. That is, the first middleware
// is treated as the outermost middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}
Файл теста содержит напечатанные шаги.
func ExampleChain() {
e := endpoint.Chain(
annotate("first"),
annotate("second"),
annotate("third"),
)(myEndpoint)
if _, err := e(ctx, req); err != nil {
panic(err)
}
// Output:
// first pre
// second pre
// third pre
// my endpoint!
// third post
// second post
// first post
}
var (
ctx = context.Background()
req = struct{}{}
)
func annotate(s string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println(s, "pre")
defer fmt.Println(s, "post")
return next(ctx, request)
}
}
}
func myEndpoint(context.Context, interface{}) (interface{}, error) {
fmt.Println("my endpoint!")
return struct{}{}, nil
}
Насколько я понимаю, annotate
сначала должны быть выполнены три метода, за которыми следует endpoint.Chain
метод и myEndpoint
которые должны быть выполнены в конце. Также, поскольку pre
сначала печатается и когда функция возвращает «сообщение», должно следовать в соответствии с defer
объяснением в документе go:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
Итак, что я ожидаю увидеть, так это
// Output:
// first pre
// first post
// second pre
// second post
// third pre
// third post
// my endpoint!
Короче говоря, мои вопросы таковы:
- почему
first pre
не следуетfirst post
, то же самое, чтоsecond
third
. - порядок
post
s обратный.endpoint.Chain
обратное выполнение спискаannotate
возвращаемых значений, ноannotate
методы оцениваются в первую очередь, верно? Не говоря уже о том,pre
что буквы s печатаются, что означает, что сначала выполняются внутренние функции
Комментарии:
1. Как может внешняя функция вернуться до того, как будет вызвана внутренняя?
Ответ №1:
Отложенная функция выполняется последней в функции после оператора return, поэтому annotate
функция сначала запускается next
, и только после этого возвращается, будет выполняться отложенная функция. Основываясь на вашем коде, порядок, который он должен напечатать, таков:
first pre
second pre
third pre
my endpoint
third post
second post
first post
Комментарии:
1. похоже, это не ответ на мой первый вопрос. Из опубликованных комментариев это может объяснить только, почему
post
это должно произойти послеpre
, но не объясняет, почемуfirst
иsecond
чередование?2.Это происходит потому, что отложенные функции запускаются после
next
вызова и возвращаются. Итак, сначала предварительное, затем второе, предварительное и т. Д., И первое сообщение будет напечатано только после второго возврата.3. не
next
следует ли немедленно ответить на первый звонок? Я не видел, чтобы это зависело от второго4. Основываясь на ваших конструкциях, первый вызывает второй, второй вызывает третий, а затем третий вызывает конечную точку. Затем возвращается конечная точка, затем третья, затем вторая, а затем первая.
5. может быть, глупый вопрос, не могли бы вы указать мне строку кода, в которой есть логика, о которой вы упомянули? Метод аннотирования просто возвращает функции и не запускает ни одной строки кода.
Chain
Метод выполняет три функции в обратном порядке
Ответ №2:
Вот ваш пример, превращенный во что-то, что работает на игровой площадке Go.
Обратите внимание, что если вы вызываете defer
более одного раза в данной функции, каждый отложенный вызов выполняется в порядке ЛИФО. Поэтому, если вы хотите использовать defer
, чтобы убедиться, что сначала вам post
позвонят, а затем next
сработает, подумайте о замене:
defer fmt.Println(s, "post")
next(ctx, request)
с:
defer next(ctx, request)
defer fmt.Println(s, "post)
Конечно, в вашем случае вы хотите вернуть то, что next
возвращается, что создает небольшую проблему. Чтобы обойти это в реальных случаях, вам нужна небольшая функция и некоторые именованные возвращаемые значения:
defer func() { i, e = next(ctx, request) }()
где i
и e
являются именованными возвращаемыми значениями.
Вот тот же код, превращенный в новый пример, в котором отложенные вызовы выполняются в нужном порядке. В данном случае пример довольно глупый, так как ничто не паникует и между ними нет «опасных шагов», поэтому все, что нам действительно нужно, это выполнить два fmt.Println
вызова последовательно, без использования defer
. Но если бы мы могли паниковать между fmt.Println(s, "pre")
разделом «Почта» и «Почта», тогда это могло бы иметь смысл.