Go – Defer

Defer

Câu lệnh defer là cách tiện lợi để chạy một đoạn code trước khi hàm trả về. Defer luôn được kích hoạt ở cuối hàm, thậm trí kể cả những đoạn code không được kiểm soát lỗi và là nguyên nhân gây chương trình bị tạm dừng, thì nó vẫn đảm bảo rằng Defer code được chạy. Định nghĩa này nghe có vẻ phức tạp nhưng thực tế nó tương đối dễ hiểu thông qua ví dụ

package main

import (  
    "fmt"
)

func finished() {  
    fmt.Println("Finished finding largest")
}

func largest(nums []int) {  
    defer finished()    
    fmt.Println("Started finding largest")
    max := nums[0]
    for _, v := range nums {
        if v > max {
            max = v
        }
    }
    fmt.Println("Largest number in", nums, "is", max)
}

func main() {  
    nums := []int{78, 109, 2, 563, 300}
    largest(nums)
}

Kết quả

Started finding largest  
Largest number in [78 109 2 563 300] is 563  
Finished finding largest

Ví dụ bên trên là tìm số lớn nhất của slice truyền vào. Hàm largest nhận int slice như một tham số truyền vào rồi in ra số lớn nhất. Hàng đầu tiên của hàm có câu lệnh defer finished(), nghĩa là hàm finished() sẽ chỉ được gọi khi hàm largest trả về.

Phương thức

Defer không chỉ giới hạn sử dụng trong hàm, nó hoàn toàn có thể sử dụng cho gọi phương thức. Hãy thử xem ví dụ sau

package main

import (  
    "fmt"
)


type person struct {  
    firstName string
    lastName string
}

func (p person) fullName() {  
    fmt.Printf("%s %s",p.firstName,p.lastName)
}

func main() {  
    p := person {
        firstName: "John",
        lastName: "Smith",
    }
    defer p.fullName()
    fmt.Printf("Welcome ")  
}

Kết quả

Welcome John Smith

Trong hàm main(), chúng ta gọi defer p.fullname(), nên fullname của của person được in ra sau cùng, khi chương trình chạy xong.

Tính toán tham số

Tham số của hàm Defer được tính toán khi câu lệnh defer được chạy và không phải là khi gọi hàm mới thực hiện tính toán. Hãy cùng xem ví dụ sau

package main

import (  
    "fmt"
)

func printA(a int) {  
    fmt.Println("value of a in deferred function", a)
}
func main() {  
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a)

}

Biến a được khởi tạo với giá trị bằng 5. Khi câu lệnh defer được chạy, giá trị của a5 và vì thế đây sẽ là tham số được truyền vào hàm printA. Chúng ta cũng đã thay đổi giá trị của a nằm dưới defer, hàng tiếp theo phía sau printA.

Kết quả

value of a before deferred function call 10  
value of a in deferred function 5

Stack defer

Khi một hàm gọi nhiều defer, chúng sẽ được thêm vào stack và được gọi theo thứ tự Last In First Out (LIFO)

Chúng ta sẽ viết một ví dụ dùng để in ra một chuỗi đảo ngược sử dụng stack

package main

import (  
    "fmt"
)

func main() {  
    name := "Naveen"
    fmt.Printf("Original String: %s\n", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
}

Trong đoạn code bên trên, vòng lặp for range thực hiện lặp một chuỗi và gọi defer fmt.Printf("%c", v). Các defer này sẽ được thêm vào stack và gọi theo thứ tự LIFO và do đó chuỗi được in ra theo thứ tự đảo ngược

Kết quả

Original String: Naveen  
Reversed String: neevaN

Panic

Hãy cùng xem ví dụ sau về code Panic

package main

import "fmt"

func main() {
	deferExample()
	fmt.Println("Returned from deferExample.")
	deferPanicExample()
	fmt.Println("Returned from deferPanicExample.")
}

func deferExample() {
	defer fmt.Println("Deferred log.")
	fmt.Println("Print line.")
}

func deferPanicExample() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recover from panic", r)
		}
	}()
	fmt.Println("Call recurse.")
	recursePanic(0)
	fmt.Println("Return normally from recurse.")
}

func recursePanic(count int) {
	if count > 3 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", count))
	}
	defer fmt.Println("Defer log in recurse", count)
	fmt.Println("Print line in recurse", count)
	recursePanic(count + 1)
}

Code bên trên sử dụng đệ quy và chúng ta sẽ có một panic khi count lớn hơn 3. fmt.Sprintf dùng để định dạng một string dựa trên giá trị. Điều này là bắt buộc vì tham số của hàm panic yêu cầu một string.

Ngoài ra hàm recover() có phép chúng ta lấy lại từ panic xuống stack được gọi. recover này gọi đến hàm defer anonymous, chúng ta cần đảm bảo rằng nó sẽ luôn luôn được gọi khi gọi deferPanicExample.

Kết quả

Print line.
Deferred log.
Returned from deferExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Panicking!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Recover from panic 4
Returned from deferPanicExample.

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.