Отложите заказ на выполнение функции

# #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!
 

Короче говоря, мои вопросы таковы:

  1. почему first pre не следует first post , то же самое, что second third .
  2. порядок 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") разделом «Почта» и «Почта», тогда это могло бы иметь смысл.