一、從回調(diào)到Promise,為什么要使用它
當(dāng)我們在執(zhí)行異步任務(wù)的時候,往往會使用一個回調(diào)函數(shù),它會在異步任務(wù)結(jié)束時被調(diào)用,它有一個共同點就是會在未來被執(zhí)行。例如在node.js中異步讀取文件:
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
又如我們寫了個ajax請求函數(shù):
// 簡化版,請忽略細(xì)節(jié)
const request = function(callback){
let xhr = new XMLHttpRequest();
// .....
xhr.onreadystatechange = function(){
callback(xhr)
}
}
//調(diào)用
request(function(xhr){
if (xhr.readyState === 4 && xhr.status === 200) {
// ....
} else {
//....
}
})
甚至是一個定時器任務(wù):
const doSomethingLater = function(delay, callback){
setTimeout(callback, delay*1000)
}
// 調(diào)用
doSomethingLater(1, function(){
// .....
})
這樣看使用回調(diào)的方式似乎沒什么問題,但是當(dāng)回調(diào)函數(shù)中又有異步任務(wù)時,就可能出現(xiàn)多層回調(diào),也就是回調(diào)地獄的問題。多層回調(diào)降低了代碼的可讀性和可維護(hù)性。
Promise為我們做了什么
簡單來講,Promise將異步任務(wù)包裝成對象,將異步任務(wù)完成后要執(zhí)行的函數(shù)傳給then方法,通過resolve來調(diào)用該函數(shù)。如上面定時器任務(wù)可以改寫成:
const doSomethingLater = function(delay){
return new Promise((resolve)=>{
setTimeout(()=>{ resolve() }, delay*1000)
})
}
doSomethingLater(1)
.then(()=>{
console.log('任務(wù)1')
})
如果定時任務(wù)中又執(zhí)行定時任務(wù),就可以這樣寫,而不是再嵌套一層回調(diào):
doSomethingLater(1)
.then(() => {
console.log('任務(wù)1')
return doSomethingLater(1)
})
.then(() => {
console.log('任務(wù)2')
})
Promise的作用:
- 把異步任務(wù)完成后的處理函數(shù)換個位置放:傳給then方法,并支持鏈?zhǔn)秸{(diào)用,避免層層回調(diào)。
- 捕獲錯誤:不管是代碼錯誤還是手動reject(),都可以用一個函數(shù)來處理這些錯誤。
二、你可能不知道的Promise細(xì)節(jié)
用了Promise就是異步代碼
就算你的代碼原來沒有異步操作:
Promise.resolve()
.then(() => {
console.log(1)
})
console.log(2)
// 2
// 1
這一點可以查一下事件循環(huán)相關(guān)知識
catch的另一種寫法
Promise.reject('error')
.then(() => {
})
.catch((err) => {
console.log(err)
})
// 等價于
Promise.reject('error')
.then(() => {
})
.then(null, (err) => {
console.log(err)
})
// 或者
Promise.reject('error')
.then(() => {
}, (err) => {
console.log(err)
})
其實catch只是個語義化的語法糖,我們也可以直接用then來處理錯誤。
then 或 catch 方法始終返回promise對象
then方法第一個參數(shù)和第二個參數(shù)(或catch的參數(shù)),只是調(diào)用條件不同。
Promise.resolve()
.then(() => {
return 1
})
.then((res) => {
console.log(res) // 1
})
Promise.resolve()
.then(() => {
// 不返回什么
})
.then((res) => {
console.log(res) // undefined,因為函數(shù)默認(rèn)返回undefined
})
如果是返回一個promise對象:
Promise.resolve()
.then(() => {
return new Promise((resolve) => {
resolve(2)
})
})
.then((res) => {
console.log(res) // 2, 根據(jù)返回的那個promise對象的狀態(tài)來
})
我們可以通過包裝,使一個promise對象的最后狀態(tài)始終是成功的: 例如:
const task = () => {
return new Promise((resolve, reject) => {
// ....
})
}
task()
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
原本調(diào)用task函數(shù)時需要在外面加一層catch捕獲錯誤,其實可以包裝一下:
const task = () => {
return new Promise((resolve, reject) => {
// ....
})
.then((res) => {
return {
status: 'success',
value: res
}
})
.catch((err) => {
return {
status: 'fail',
value: err
}
})
}
// 現(xiàn)在調(diào)用就會更簡潔!
task()
.then((result) => {
console.log(result)
})
catch中報錯也可以在后面繼續(xù)捕獲,因為catch也是返回promise對象嘛
Promise.reject('first error')
.catch((err) => {
console.log(err)
throw new Error('second error')
})
.then(null, (err) => {
console.log(err)
})
三、await:新的語法糖
await使得異步代碼更像是同步代碼,對串行的異步調(diào)用寫起來更自然。await后面跟一個值或promise對象,如果是一個值,那么直接返回。如果是一個promise對象,則接受promise對象成功后的返回值。或者在await后面調(diào)用一個async函數(shù)
const task = async () => {
return new Promise((resolve, reject) => {
resolve(1)
})
}
const handle = async () => {
let value = await task()
console.log(value)
}
handle() // 1
使用await要注意錯誤的捕獲,因為await只關(guān)心成功:
const task = async () => {
return new Promise((resolve, reject) => {
reject(1)
})
}
const handle = async () => {
let value = await task()
console.log(value)
}
handle()
這樣寫會報錯,所以要么在task函數(shù)中捕獲錯誤,要么就在task調(diào)用時捕獲,像這樣:
const task = async () => {
return new Promise((resolve, reject) => {
reject(1)
})
}
const handle = async () => {
let value = await task().catch((err) => { console.log(err) })
console.log(value) // undefine, 因為錯誤處理函數(shù)沒返回值,你也可以返回錯誤信息,這樣就會通過value拿到
}
需要注意的是,使用await也會使代碼變成異步的:
const handle = async () => {
let value = await 1
console.log(value)
}
handle()
console.log(2)
// 2
// 1
完~
|