假設我們有三個請求,req1,req2, req3,三個請求後者依賴前者的請求結果。我們先使用Promise
封裝一個異步請求的方法。
Promise 異步請求
使用Promise
可以非常容易的封裝一個異步處理的業務,通過reslove/reject
兩個callback
來返回執行結果。
我們使用 Promise
封裝一個 http get
方法。
// 返回一個 Promise 對象(PromiseStatus: pending)
function asyncHttpGet(url) {
return new Promise((resolve, reject) => {
const request = new Request(url, {method: 'GET'})
// 使用 fetch 請求
fetch(request)
.then(response => {
if (200 == response.status) {
return response.text()
} else {
// goto catch
throw new Error("request failed: " + response.status)
}
}).then(html => {
// console.log(html)
resolve(url + " get success")
}).catch(err => {
reject(err)
});
})
}
fetch
返回的其實就是一個Promise
對象,上例是想演示下resolve/reject
的使用上下文,如果你早已get
,下面給出直接使用fetch
的方式:
async function asyncHttpGetV2(url) {
const request = new Request(url, {method: 'GET'})
try {
let res = await fetch(request)
.then(response => response.blob())
.then(blob => {
console.log(blob)
// Promise resolve
return blob
}).catch(err => {
// Promise resolve
throw err
});
// Promise resolve
return res;
} catch (err) {
// Promise reject
throw err
}
}
可以發現,fetch
中return
代替了resolve
,throw
代替了reject
,而async
同fetch
一樣,也是返回了一個 Promise
對象,所以async
中的return/throw
是否也會與自己的返回的Promise
對象有關係呢?
回調地獄
Promise
可以優雅的實現異步,但 Promise.then().catch()
的鏈式結構也帶來了回調地獄的問題。如下,我們回調了3層,才能開始寫業務邏輯。
var url = window.location.href
// 雖然異步了 但 callback hell
asyncHttpGet(url).then(res => {
var res1 = res
asyncHttpGet(url).then(res => {
var res2 = res
asyncHttpGet(url).then(res => {
var res3 = res
console.log(res1, res2, res3);
// todo 業務
}).catch(err => {
console.log(err)
})
}).catch(err => {
console.log(err)
})
}).catch(err => {
console.log(err)
})
async/await
藉助 aysnc/await
解決回調地獄的問題,實現同步風格的異步邏輯,這裏希望大家能理解透2 & 3
兩條總結:
-
aysnc
返回的也是一個Promise
對象。 - 如果返回了
return 標量
或throw Error
則返回{PromiseStatus: resolved/rejected}
的Promise
對象。 - 如果遇到了
await
裝飾的Promise
,則返回{PromiseStatus: pending}
的Promise
。並等待此Promise
的執行結果:如果Promise
觸發了resolve
則獲取結果並繼續向下執行;如果Promise
觸發了reject
則拋出一個異常。所以我們在使用時應將代碼使用try...catch
封裝。 -
await
關鍵字只能在async
內使用,await
接受一個Promise
對象,並將其以{PromiseStatus: pending}
的狀態返回。
var url = window.location.href
async function getUrls(url1, url2, url3) {
try {
// req1 success or throw error (promise reject)
let res1 = await asyncHttpGet(url1);
// req2 success or throw error (promise reject)
let res2 = await asyncHttpGet(url2);
// req3 success or throw error (promise reject)
let res3 = await asyncHttpGet(url3);
// 三個異步請求都成功 獲取最終結果
return [res1, res2, res3].join("\n")
} catch(err) {
// 出現錯誤,做一些處理
console.log(err)
throw err
}
}
// 如此 3 個 Promise 請求在 async/await 的封裝下變成了一個同步書寫風格的異步請求
getUrls(url, url, url).then(res => {
console.log(res)
// todo 業務
}).catch(err => {
console.log(err)
})
console.log("request has been sended, and waiting for res")
async 返回的是 Promise
對象,所以我們還可以繼續使用 async\await
封裝異步到同步風格。
async function getUrlsMore(url1, url2) {
try {
let getUrls1 = await getUrls(url1, url1, url1)
let getUrls2 = await getUrls(url2, url2, url2)
// Promise resolve
return [getUrls1, getUrls2].join("\n")
} catch (err) {
// Promise reject
throw err
}
}
getUrlsMore(url, url).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
async/await 和 Promise 的關係
async/await
和 Promise
的關係非常的巧妙,await
必須在async
內使用,並裝飾一個Promise
對象,async
返回的也是一個Promise
對象。
async/await
中的return/throw
會代理自己返回的Promise
的resolve/reject
,而一個Promise
的resolve/reject
會使得await
得到返回值或拋出異常。
如果方法內無await
節點
return
一個字面量
則會得到一個{PromiseStatus: resolved}
的Promise
。throw
一個Error
則會得到一個{PromiseStatus: rejected}
的Promise
。
如果方法內有await
節點async
會返回一個{PromiseStatus: pending}
的Promise
(發生切換,異步等待Promise
的執行結果)。Promise
的resolve
會使得await
的代碼節點獲得相應的返回結果,並繼續向下執行。Promise
的reject
會使得await
的代碼節點自動拋出相應的異常,終止向下繼續執行。
示例:
方法內無await
節點
// 沒有 await 修飾的 Promise
async function foo() {
if (Math.ceil(Math.random() * 10) > 5) {
// {PromiseStatus: resolved}
return "hello world"
} else {
// {PromiseStatus: rejected}
throw new Error("something wrong!")
}
}
var fooPromise = foo()
console.log(fooPromise)
fooPromise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
resolved
rejected
方法內有await
節點
注意Promise
內的resolve/reject
對 await
節點的作用。
async function bar() {
try {
// await 返回 {PromiseStatus: pending}
let res = await new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.ceil(Math.random() * 10) > 5) {
// await 獲得結果並繼續執行
resolve("success")
} else {
// await 中斷執行並拋出異常
reject("failed")
}
}, 2000)
})
// resolve {PromiseStatus: resolved}
return res
} catch (err) {
// reject {PromiseStatus: rejected}
throw err
}
}
var barPromise = bar()
// 查看 barPromise 的 PromiseStatus
console.log(barPromise)
barPromise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
await
配合fetch
的實例
then/catch
返回的也是Promise
對象,在then/catch
內使用return/throw
來決定返回的Promise
是resolved/rejected
。
// 沒有 await 修飾的 Promise
async function bar() {
try {
// await 返回 {PromiseStatus: pending}
let res1 = await fetch(window.location.href).then(res => {
if (200 == res.status) {
// Promise resolve
return "request success"
} else {
// goto catch
throw "request failed" + res.status
}
}).catch(err => {
// Promise reject
throw err
})
let res2 = await fetch(window.location.href).then(res => {
if (200 == res.status) {
// Promise resolve
return "request success"
} else {
// goto catch
throw "request failed" + res.status
}
}).catch(err => {
// Promise reject
throw err
})
let res3 = await fetch(window.location.href).then(res => {
if (200 == res.status) {
// Promise resolve
return "request success"
} else {
// goto catch
throw "request failed" + res.status
}
}).catch(err => {
// Promise reject
throw err
})
// 三個請求都成功 則返回相應的數據 Promise resolved
return [res1, res2, res3].join("\n")
} catch (err) {
// Promise rejected
throw err
}
}
var barPromise = bar()
// 查看 barPromise 的 PromiseStatus
console.log(barPromise)
// Promise reject 拋出異常 需要使用 catch 捕捉
barPromise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})