與Promise血脈相連的async/await

limingru發表於2018-02-27

async/await是JavaScript為了解決非同步問題而提出的一種解決方案,許多人將其稱為非同步的終極解決方案。JavaScript的發展也經歷了回撥、Promise、async/await三個階段,本篇文章記錄了我自己對於async/await的理解。因為async/await的使用離不開Promise,如果對於Promise不熟悉的話,可以看下這篇介紹:前端萌新眼中的Promise及使用

一、async/await的具體使用規則

在我們處理非同步的時候,比起回撥函式,Promise的then方法會顯得較為簡潔和清晰,但是在處理多個彼此之間相互依賴的請求的時候,就會顯的有些累贅。這時候,用async和await更加優雅,後面會詳情說明。

async/await使用規則一:凡是在前面新增了async的函式在執行後都會自動返回一個Promise物件

例子:

async function test() {
    
}

let result = test()
console.log(result)  //即便程式碼裡test函式什麼都沒返回,我們依然打出了Promise物件
複製程式碼

async/await使用規則二:await必須在async函式裡使用,不能單獨使用

錯誤的例子:

function test() {
    let result = await Promise.resolve('success')
    console.log(result)
}

test()   //執行以後會報錯
複製程式碼

正確的例子:

async test() {
    let result = await Promise.resolve('success')
    console.log(result)
}
test()
複製程式碼

async/await使用規則三:await後面需要跟Promise物件,不然就沒有意義,而且await後面的Promise物件不必寫then,因為await的作用之一就是獲取後面Promise物件成功狀態傳遞出來的引數。

正確的例子:

function fn() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success')
        })
    })
}

async test() {
    let result = await fn() //因為fn會返回一個Promise物件
    console.log(result)    //這裡會打出Promise成功後傳遞過來的'success'
}

test()
複製程式碼

沒有意義的例子(不會報錯):

async test() {
    let result = await 123
    console.log(result)
}

test()
複製程式碼

二、async/await的錯誤處理方式

關於錯誤處理,如規則三所說,await可以直接獲取到後面Promise成功狀態傳遞的引數,但是卻捕捉不到失敗狀態。在這裡,我們通過給包裹await的async函式新增then/catch方法來解決,因為根據規則一,async函式本身就會返回一個Promise物件。

一個包含錯誤處理的完整的async/await例子:

let promiseDemo = new Promise((resolve, reject) => {
    setTimeout(() => {
        let random = Math.random()
        if (random >= 0.5) {
            resolve('success')
        } else {
            reject('failed')
        }   
    }, 1000)
})

async function test() {
    let result = await promiseDemo
    return result  //這裡的result是promiseDemo成功狀態的值,如果失敗了,程式碼就直接跳到下面的catch了
}

test().then(response => {
    console.log(response) 
}).catch(error => {
    console.log(error)
})

複製程式碼

上面的程式碼需要注意兩個地方,一是async函式需要主動return一下,如果Promise的狀態是成功的,那麼return的這個值就會被下面的then方法捕捉到;二是,如果async函式有任何錯誤,都被catch捕捉到!

三、同步與非同步

在async函式中使用await,那麼await這裡的程式碼就會變成同步的了,意思就是說只有等await後面的Promise執行完成得到結果才會繼續下去,await就是等待,這樣雖然避免了非同步,但是它也會阻塞程式碼,所以使用的時候要考慮周全。

比如下面的程式碼:

function fn(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${name}成功`)
        }, 1000)
    })
}

async function test() {
    let p1 = await fn('小紅')
    let p2 = await fn('小明')
    let p3 = await fn('小華')
    return [p1, p2, p3]
}

test().then(result => {
    console.log(result)
}).catch(result => {
    console.log(result)
})
複製程式碼

這樣寫雖然是可以的,但是這裡await會阻塞程式碼,每個await都必須等後面的fn()執行完成才會執行下一行程式碼,所以test函式執行需要3秒。如果不是遇到特定的場景,最好還是不要這樣用。

三、一個小測試

寫到這裡,突然想起Promise的程式碼執行順序也是挺需要注意的。

請看下面的程式碼,執行完以後打出的數字的順序是怎樣的呢?

console.log(1)
let promiseDemo = new Promise((resolve, reject) => {
    console.log(2)
    setTimeout(() => {
        let random = Math.random()
        if (random >= 0.2) {
            resolve('success')
            console.log(3)
        } else {
            reject('failed')
            console.log(3)
        }   
    }, 1000)
})

async function test() {
    console.log(4)
    let result = await promiseDemo
    return result
}

test().then(result => {
    console.log(5)
}).catch((result) => {
    console.log(5)
})

console.log(6)
複製程式碼

答案是:1 2 4 6 3 5

四、一個適合使用async/await的業務場景

在前端程式設計中,我們偶爾會遇到這樣一個場景:我們需要傳送多個請求,而後面請求的傳送總是需要依賴上一個請求返回的資料。對於這個問題,我們既可以用的Promise的鏈式呼叫來解決,也可以用async/await來解決,然而後者會更簡潔些。

使用Promise鏈式呼叫來處理:

function request(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time)
        }, time)
    })
}

request(500).then(result => {
    return request(result + 1000)
}).then(result => {
    return request(result + 1000)
}).then(result => {
    console.log(result)
}).catch(error => {
    console.log(error)
}) 
複製程式碼

使用async/await的來處理:

function request(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time)
        }, time)
    })
}

async function getResult() {
    let p1 = await request(500)
    let p2 = await request(p1 + 1000)
    let p3 = await request(p2 + 1000)
    return p3
}

getResult().then(result => {
    console.log(result)
}).catch(error => {
    console.log(error)
})
複製程式碼

相對於使用then不停地進行鏈式呼叫, 使用async/await會顯的更加易讀一些。

五、在迴圈中使用await

如果在是迴圈中使用await,就需要牢記一條:必須在async函式中使用

for...of中使用await:

let request = (time) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(time)
        }, time)
    })
}

let times = [1000, 500, 2000]
async function test() {
    let result = []
    for (let item of times) {
        let temp = await request(item)
        result.push(temp)
    }
    return result
}

test().then(result => {
    console.log(result)
}).catch(error => {
    console.log(error)
})
複製程式碼

上面就是我今天關於async/await理解的記錄,筆者作為一隻程式設計師生活只有半年,以上內容估計還有錯誤之處,如果掘金上的朋友有看到,還望不吝指出!謝謝您的觀看!

相關文章