De nos jours, le goulot d'étranglement le plus courant dans l'exécution des applications concerne les requêtes réseau. La requête réseau ne prend que quelques millisecondes, mais l’attente de la réponse est cent fois plus longue. Ainsi, si vous effectuez plusieurs requêtes réseau, les exécuter toutes en parallèle est la meilleure option pour réduire la latence. Future/Promise est l’un des moyens d’atteindre cet objectif.
Un futur signifie que vous avez besoin de quelque chose "dans le futur" (généralement le résultat d'une requête réseau), mais vous devez lancer une telle requête maintenant, et la requête sera exécutée de manière asynchrone. Ou pour le dire autrement, vous devez effectuer une requête asynchrone en arrière-plan.
Le modèle Future/Promise a des implémentations correspondantes dans plusieurs langues. Par exemple, ES2015 a Promise et async-await, Scala a intégré Future et enfin, Golang a goroutine et canal pour réaliser des fonctions similaires. Une implémentation simple est donnée ci-dessous.
//RequestFuture, http request promise. func RequestFuture(url string) <-chan []byte { c := make(chan []byte, 1) go func() { var body []byte defer func() { c <- body }() res, err := http.Get(url) if err != nil { return } defer res.Body.Close() body, _ = ioutil.ReadAll(res.Body) }() return c } func main() { future := RequestFuture("https://api.github.com/users/octocat/orgs") body := <-future log.Printf("reponse length: %d", len(body)) }
RequestFuture
La méthode renvoie un canal À l'heure actuelle, la requête http s'exécute toujours de manière asynchrone en arrière-plan goroutine. La méthode principale peut continuer à exécuter d'autres codes, comme déclencher d'autres Future
, etc. Lorsque des résultats sont nécessaires, nous devons lire les résultats du canal. Si la requête http n'est pas renvoyée, la goroutine actuelle sera bloquée jusqu'à ce que le résultat soit renvoyé.
Cependant, la méthode ci-dessus présente encore certaines limites. L'erreur ne peut pas être retournée. Dans l'exemple ci-dessus, si une erreur se produit dans la requête http, la valeur de body sera nulle/vide. Cependant, comme le canal ne peut renvoyer qu’une seule valeur, vous devez créer une structure distincte pour envelopper les deux résultats renvoyés.
Résultat après modification :
// RequestFutureV2 return value and error func RequestFutureV2(url string) func() ([]byte, error) { var body []byte var err error c := make(chan struct{}, 1) go func() { defer close(c) var res *http.Response res, err = http.Get(url) if err != nil { return } defer res.Body.Close() body, err = ioutil.ReadAll(res.Body) }() return func() ([]byte, error) { <-c return body, err } }
Cette méthode renvoie deux résultats, résolvant les limitations de la première méthode. Lorsqu'elle est utilisée, elle ressemble à ceci :
func main() { futureV2 := RequestFutureV2("https://api.github.com/users/octocat/orgs") // not block log.Printf("V2 is this locked again") bodyV2, err := futureV2() // block if err == nil { log.Printf("V2 response length %d\n", len(bodyV2)) } else { log.Printf("V2 error is %v\n", err) } }
L'avantage de la modification ci-dessus est que la méthode futureV2()
peut être appelée plusieurs fois. Et les deux peuvent renvoyer le même résultat.
Cependant, si vous souhaitez utiliser cette méthode pour implémenter de nombreuses fonctions asynchrones différentes, vous devez écrire beaucoup de code supplémentaire. Nous pouvons écrire une méthode util pour surmonter cette difficulté. Lorsque
// Future boilerplate method func Future(f func() (interface{}, error)) func() (interface{}, error) { var result interface{} var err error c := make(chan struct{}, 1) go func() { defer close(c) result, err = f() }() return func() (interface{}, error) { <-c return result, err } }
appelle la méthode Future
, elle effectuera de nombreuses astuces de canal dans la pièce. Afin d'atteindre des objectifs universels, il existe une conversion de type de []buyte
->interface{}
->[]byte
. Si une erreur se produit, une panique d'exécution sera déclenchée.