JavaScript異步之從promise到await

1、從回調到Promise,爲何要使用它

當咱們在執行異步任務的時候,每每會使用一個回調函數,它會在異步任務結束時被調用,它有一個共同點就是會在將來被執行。例如在node.js中異步讀取文件:node

fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  console.log(data);
});
複製代碼

又如咱們寫了個ajax請求函數:ajax

// 簡化版,請忽略細節
const request = function(callback){
  let xhr = new XMLHttpRequest();
   // .....
  xhr.onreadystatechange = function(){
    callback(xhr)              
  }
}
//調用
request(function(xhr){
  if (xhr.readyState === 4 && xhr.status === 200) {
      // ....
  } else {
      //....
  }  
})

複製代碼

甚至是一個定時器任務:promise

const doSomethingLater = function(delay, callback){
    setTimeout(callback, delay*1000)
}
// 調用
doSomethingLater(1, function(){
  // .....
})
複製代碼

這樣看使用回調的方式彷佛沒什麼問題,可是當回調函數中又有異步任務時,就可能出現多層回調,也就是回調地獄的問題。多層回調下降了代碼的可讀性和可維護性。bash

Promise爲咱們作了什麼

簡單來說,Promise將異步任務包裝成對象,將異步任務完成後要執行的函數傳給then方法,經過resolve來調用該函數。如上面定時器任務能夠改寫成:異步

const doSomethingLater = function(delay){
    return new Promise((resolve)=>{
      setTimeout(()=>{ resolve() }, delay*1000)
    })
}
doSomethingLater(1)
    .then(()=>{
      console.log('任務1')
    })
複製代碼

若是定時任務中又執行定時任務,就能夠這樣寫,而不是再嵌套一層回調:async

doSomethingLater(1)
    .then(() => {
        console.log('任務1')
        return doSomethingLater(1)
    })
    .then(() => {
        console.log('任務2')
    })
複製代碼

Promise的做用:函數

  • 把異步任務完成後的處理函數換個位置放:傳給then方法,並支持鏈式調用,避免層層回調。
  • 捕獲錯誤:無論是代碼錯誤仍是手動reject(),均可以用一個函數來處理這些錯誤。

2、你可能不知道的Promise細節

用了Promise就是異步代碼

就算你的代碼原來沒有異步操做:ui

Promise.resolve()
    .then(() => {
        console.log(1)
    })
console.log(2)
// 2
// 1
複製代碼

這一點能夠查一下事件循環相關知識spa

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來處理錯誤。code

then 或 catch 方法始終返回promise對象

then方法第一個參數和第二個參數(或catch的參數),只是調用條件不一樣。

Promise.resolve()
    .then(() => {
        return 1
    })
    .then((res) => {
        console.log(res) // 1
    })
    
Promise.resolve()
    .then(() => {
       // 不返回什麼
    })
    .then((res) => {
        console.log(res) // undefined,由於函數默認返回undefined 
    })
   
複製代碼

若是是返回一個promise對象:

Promise.resolve()
    .then(() => {
        return new Promise((resolve) => {
            resolve(2)
        })
    })
    .then((res) => {
        console.log(res) // 2, 根據返回的那個promise對象的狀態來
    })
複製代碼

咱們能夠經過包裝,使一個promise對象的最後狀態始終是成功的: 例如:

const task = () => {
    return new Promise((resolve, reject) => {
        // ....
    })
}
task()
    .then((res) => {
        console.log(res)
    })
    .catch((err) => {
        console.log(err)
    })
複製代碼

本來調用task函數時須要在外面加一層catch捕獲錯誤,其實能夠包裝一下:

const task = () => {
    return new Promise((resolve, reject) => {
        // ....
    })
        .then((res) => {
            return {
                status: 'success',
                value: res
            }
        })
        .catch((err) => {
            return {
                status: 'fail',
                value: err
            }
        })
}
// 如今調用就會更簡潔!
task()
    .then((result) => {
        console.log(result)
    })

複製代碼

catch中報錯也能夠在後面繼續捕獲,由於catch也是返回promise對象嘛

Promise.reject('first error')
    .catch((err) => {
        console.log(err)
        throw new Error('second error')
    })
    .then(null, (err) => {
        console.log(err)
    })
複製代碼

3、await:新的語法糖

await使得異步代碼更像是同步代碼,對串行的異步調用寫起來更天然。await後面跟一個值或promise對象,若是是一個值,那麼直接返回。若是是一個promise對象,則接受promise對象成功後的返回值。或者在await後面調用一個async函數

const task = async () => {
    return new Promise((resolve, reject) => {
        resolve(1)
    })
}
const handle = async () => {
    let value = await task()
    console.log(value)
}
handle() // 1
複製代碼

使用await要注意錯誤的捕獲,由於await只關心成功

const task = async () => {
    return new Promise((resolve, reject) => {
        reject(1)
    })
}
const handle = async () => {
    let value = await task()
    console.log(value)
}
handle()
複製代碼

這樣寫會報錯,因此要麼在task函數中捕獲錯誤,要麼就在task調用時捕獲,像這樣:

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, 由於錯誤處理函數沒返回值,你也能夠返回錯誤信息,這樣就會經過value拿到
}
複製代碼

須要注意的是,使用await也會使代碼變成異步的:

const handle = async () => {
    let value = await 1
    console.log(value)
}
handle()
console.log(2)
// 2
// 1
複製代碼

完~

相關文章
相關標籤/搜索