壓箱底筆記:Promise和Async/await的理解和使用

首發於SF,未經受權,禁止各類形式轉載javascript

之前學習寫的筆記,感受還不錯,如今發出來,但願對你有幫助。文章比較長,能夠結合目錄進行閱讀,若是文章對你有所啓發和幫助,能夠『一鍵三連』。哦,對了,我已經脫髮了...😭😭java

1. 前置知識

1.1 區別實例對象與函數對象

實例對象:new 函數產生的對象, 稱爲實例對象, 簡稱爲對象程序員

函數對象: 將函數做爲對象使用時, 簡稱爲函數對象ajax

function Fn() {}
const fn = new Fn() // fn爲實例對象
Fn.bind({}) // Fn爲函數對象

1.2 兩種類型的回調函數

同步回調編程

  • 理解:當即執行, 徹底執行完了才結束, 不會放入回調隊列中
  • 例子: 數組遍歷相關的回調函數 / Promise 的 excutor 函數

異步回調數組

  • 理解:不會當即執行, 會放入回調隊列中未來執行
  • 例子:定時器回調 / ajax 回調 / Promise 的成功|失敗的回調
const arr = [1, 2, 3]
arr.forEach(item => console.log(item)) // 同步回調, 不會放入回調隊列, 而是當即執行
console.log('forEatch()以後')
setTimeout(() => { // 異步回調, 會放入回調隊列, 全部同步執行完後纔可能執行
  console.log('timout 回調')
}, 0)
console.log('setTimeout 以後')

1.3 JS的error處理

錯誤的類型promise

  • Error:全部錯誤的父類型
  • ReferenceError:引用的變量不存在異步

    console.log(a) // ReferenceError: a is not defined
  • TypeError:數據類型不正確的錯誤async

    let b = null
    console.log(b.xxx) // TypeError: Cannot read property 'xxx' of null
  • RangeError:數據值不在其所容許的範圍內異步編程

    function fn() {
      fn()
    }
    fn() // RangeError: Maximum call stack size exceeded
  • SyntaxError:語法錯誤

    let c = """" // SyntaxError: Unexpected string

錯誤處理

  • 捕獲錯誤:try ... catch
  • 拋出錯誤:throw error

error 對象的結構

  • message 屬性:錯誤相關信息
  • stack 屬性:函數調用棧記錄信息

2. Promise 是什麼?

2.1 理解

抽象表達:Promise 是JS中進行異步編程的新的解決方案(舊的是誰?=> 純回調的形式)

具體表達:

  • 從語法上來講:Promise 是一個構造函數
  • 從功能上來講:Promise 對象用來封裝一個異步操做並能夠獲取其結果

2.2 Promise的狀態改變

Promise的狀態改變只有這2種:
Promise的狀態改變

且一個 Promise 對象只能改變一次,不管變成成功仍是失敗,都會有一個結果數據,成功的結果數據通常稱爲 value,失敗的結果數據通常稱爲 reason

2.3 Promise基本流程

Promise基本流程

2.4 Promise的基本使用

示例,若是當前時間是偶數就表明成功,不然表明失敗

// 1. 建立一個新的Promise對象
const p = new Promise((resolve, reject) => { // 執行器函數,同步執行
  // 2. 執行異步操做任務
  setTimeout(() => {
    const time = Date.now() // 若是當前時間是偶數就表明成功,不然表明失敗
    // 3.1 若是成功了,調用resolve(value)
    if (time % 2 === 0) {
        resolve('成功的數據,value = ' + time)
    } else {
        // 3.2 若是失敗了,調用reject(reason)
        reject('失敗的數據,reason = ' + time)
    }
  }, 1000);
})

p.then(value => {
  // 接受獲得成功的value數據,專業術語:onResolved
  console.log('成功的回調', value)
}, reason => {
  // 接受獲得失敗的reason數據,專業術語:onRejected
  console.log('失敗的回調', reason)
})

3. 爲何要用Promise?

3.1 指定回調函數的方式更加靈活

舊的:回調函數必須在啓動異步任務前指定

// 成功的回調函數
function successCallback(result) {
  console.log('處理成功:' + result)
}

function failureCallback(error) {
  console.log('處理失敗:' + error)
}

// 使用純回調函數
createAudioFileSync(audioSettings, successCallback, failureCallback)

Promise:啓動異步任務 => 返回 Promise 對象 => 給 Promise 對象綁定回調函數,甚至能夠在異步任務結束後指定多個

// 使用 Promise
const promise = createAudioFileSync(audioSettings)
setTimeout(() => {
  promise.then(successCallback, failureCallback)
}, 3000);

3.2 支持鏈式調用,能夠解決回調地獄問題

什麼是回調地獄?回調函數嵌套調用,外部回調函數異步執行的結果是嵌套的回掉執行條件,代碼是水平向右擴展

// 回調地獄
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult)
    }, failureCallback)
  }, failureCallback)
},

回調地獄的缺點:不便閱讀,不便於異常處理

解決方案:Promise 鏈式調用,代碼水平向下擴展

doSomething().then(function(result) {
  return doSomethingElse(result)
})
.then(function(newResult) {
  return doThirdThing(newResult)
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult)
})
.catch(failureCallback)

終極解決方案:async/await,用同步的寫法處理異步的操做

async function request() {
  try {
    const result = await doSomething()
    const newResult = await doSomethingElse(result)
    const finalResult = await doThirdThing(newResult)
    console.log('Got the final result: ' + finalResult)
  } catch (error) {
    failureCallback(error)
  }
}

4. Promise的API說明

4.1 API 說明

Promise 構造函數

Promise (excutor) {},excutor 會在 Promise 內部當即同步回調,異步操做在執行器中執行

  • excutor 函數:執行器 (resolve, reject) => {}
  • resolve 函數:內部定義成功時咱們調用的函數 value => {}
  • reject 函數:內部定義失敗時咱們調用的函數 reason => {}

Promise.prototype.then方法

(onResolved, onRejected) => {},指定用於獲得成功 value 的成功回調和用於獲得失敗 reason 的失敗回調返回一個新的 promise 對象

  • onResolved 函數:成功的回調函數 (value) => {}
  • onRejected 函數:失敗的回調函數 (reason) => {}

Promise.prototype.catch 方法

(onRejected) => {},onRejected 函數:失敗的回調函數 (reason) => {},then() 的語法糖, 至關於: then(undefined, onRejected)

Promise.resolve方法

(value) => {},value:成功的數據或 promise 對象,返回一個成功/失敗的 promise 對象

Promise.reject方法

(reason) => {},reason:失敗的緣由,返回一個失敗的 promise 對象

Promise.all方法

(promises) => {},promises:包含 n 個 promise 的數組,返回一個新的 promise, 只有全部的 promise 都成功才成功, 只要有一個失敗了就直接失敗

Promise.race方法

(promises) => {},promises: 包含 n 個 promise 的數組,返回一個新的 promise, 第一個完成的 promise 的結果狀態就是最終的結果狀態

// 產生一個成功值爲 1 的 Promise 對象
const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

// 產生一個成功值爲 2 的 Promise 對象
const p2 = Promise.resolve(2)
// 產生一個失敗值爲 3 的 Promise 對象
const p3 = Promise.reject(3)

p1.then(value => console.log(value))
p2.then(value => console.log(value))
p3.catch(reason => console.error(reason))

// const pAll = Promise.all([p1, p2])
const pAll = Promise.all([p1, p2, p3])
pAll.then(values => {
  console.log('all onResolved()', values) // all onResolved() [ 1, 2 ]
}, reason => {
  console.log('all onRejected()', reason) // all onRejected() 3
})

const race = Promise.race([p1, p2, p3])
race.then(value => {
  console.log('all onResolved()', value) 
}, reason => {
  console.log('all onRejected()', reason) 
})

4.2 Promise的幾個關鍵問題

4.2.1 如何改變Promise的狀態

resolve(value),若是當前是 pendding 就會變爲 resolved

reject(reason),若是當前是 pendding 就會變爲 rejected

拋出異常,若是當前是 pendding 就會變爲 rejected

const p = new Promise((resolve, reject) => {
  // resolve(1) // Promise 變爲 resolved 成功狀態
  // reject(2) // Promise 變爲 rejected 失敗狀態
  // Promise 變爲 rejected 失敗狀態,reason爲拋出的 error
  throw new Error('我拋出的異常') 
  // 變爲 rejected 失敗狀態,reason爲拋出的 3
  // throw 3
})

p.then(
  value => {},
  reason => { console.log('reason :', reason); }
)

4.2.2 當一個promise指定多個成功/失敗回調函數, 都會調用嗎?

當 promise 改變爲對應狀態時都會調用

const p = new Promise((resolve, reject) => {
  // 變爲 rejected 失敗狀態,reason爲拋出的 3
  throw 3
})

p.then(
  value => {},
  reason => { console.log('reason :', reason); }
)

p.then(
  value => {},
  reason => { console.log('reason2 :', reason); }
)

// 結果:
// reason : 3
// reason2 : 3

4.2.3 改變promise狀態和指定回調函數誰先誰後?

都有可能, 正常狀況下是先指定回調再改變狀態, 但也能夠先改狀態再指定回調。

如何先改狀態再指定回調?

  • 在執行器中直接調用 resolve()/reject()
  • 延遲更長時間才調用 then()

何時才能獲得數據?

  • 若是先指定的回調, 那當狀態發生改變時, 回調函數就會調用, 獲得數據
  • 若是先改變的狀態, 那當指定回調時, 回調函數就會調用, 獲得數據
// 常規:先指定回調函數,後改變狀態
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1) // 後改變狀態(同時指定數據),異步執行回調函數
  }, 1000);
}).then( // 先指定回調函數,保存當前指定的回調函數
  value => {},
  reason => { console.log('reason :', reason); }
)

// 先改狀態,後指定回調函數
new Promise((resolve, reject) => {
  resolve(1) // 先改變狀態(同時指定數據)
}).then( // 後指定回調函數,異步執行回調函數
  value => { console.log('value2:', value);},
  reason => { console.log('reason2 :', reason); }
)

const p = new Promise((resolve, reject) => {
  resolve(1) // 先改變狀態(同時指定數據)
})

setTimeout(() => {
  p.then(
    value => { console.log('value3:', value);},
    reason => { console.log('reason3 :', reason); }
  )
}, 1500);

4.2.4 promise.then()返回的新 promise 的結果狀態由什麼決定?

簡單表達:由 then()指定的回調函數執行的結果決定

詳細表達:

  • 若是拋出異常, 新 promise 變爲 rejected, reason 爲拋出的異常
  • 若是返回的是非 promise 的任意值, 新 promise 變爲 resolved, value 爲返回的值
  • 若是返回的是另外一個新 promise, 此 promise 的結果就會成爲新 promise 的結果
new Promise((resolve, reject) => {
  resolve(1)
}).then(
  value => {
    console.log('onResolved1()', value); // 1
    // return 1.1 或 
    return Promise.resolve(1.1)
    // return Promise.reject(1.1)
    // throw 1.1
  },
  reason => { 
    console.log('onRejected1()', reason);
  }
).then(
  value => { console.log('onResolved2()', value); }, // 1.1
  reason => { console.log('onRejected2()', reason) } // 1.1
)

4.2.5 promise 如何串連多個操做任務

promise 的 then() 返回一個新的 promise, 能夠開成 then() 的鏈式調用,經過 then 的鏈式調用串連多個同步/異步任務。

4.2.6 promise 異常傳透

當使用 promise 的 then 鏈式調用時, 能夠在最後指定失敗的回調,前面任何操做出了異常, 都會傳到最後失敗的回調中處理。

下面的示例代碼演示了異常傳透

new Promise((resolve, reject) => {
  // resolve(1)
  reject(1)
}).then(
  value => { 
    console.log('onResolved1()', value);
    return 2
  }
).then(
  value => { 
    console.log('onResolved2()', value);
    return 3
  }
).then(
  value => { 
    console.log('onResolved3()', value);
  }
).catch(
  reason => { 
    console.log('onRejected()', reason);  // onRejected() 1
  }
)

代碼會執行 .catch 中的代碼,但實際上代碼的執行不是執行到第 3 行就直接跳轉到 catch 裏面了,而是從第一個 then 調用向下一個個的執行(逐級傳遞),可是因爲咱們 then 裏面沒有處理異常。在 then 裏面沒寫處理異常實際上至關於默認添加了 reason => { throw reason } 或者 reason => Promise.reject(reason)

new Promise((resolve, reject) => {
  reject(1)
}).then(
  value => {  console.log('onResolved1()', value); },
  // reason => { throw reason }
  // 或者
  reason => Promise.reject(reason)
)

Promise的異常傳透示意圖

Promise的異常傳透

4.2.7 中斷 promise 鏈

當使用 promise 的 then 鏈式調用時, 在中間中斷, 再也不調用後面的回調函數。

辦法: 在回調函數中返回一個 pendding 狀態的 promise 對象

new Promise((resolve, reject) => {
 resolve(1)
}).then(
  value => { 
    console.log('onResolved1()', value);
    return new Promise(() => {}) // 返回一個 pending 的 Promise,中斷 promise 鏈
  }
).then( // 這個 then 不會執行力
  value =>  { console.log('onResolved2()', value); }
)

5. async與await

Async/await 實際上只是一種基於promises的糖衣語法糖,Async/await 和 promises同樣,都是非堵塞式的,Async/await 讓異步代碼更具同步代碼風格,這也是其優點所在。

  • async function 用來定義一個返回 AsyncFunction 對象的異步函數。異步函數是指經過事件循環異步執行的函數,它會經過一個隱式的 Promise 返回其結果,。若是你在代碼中使用了異步函數,就會發現它的語法和結構會更像是標準的同步函數。MDN async_function
  • await 操做符用於等待一個Promise 對象。它只能在異步函數 async function 中使用。MDN await

5.1 async函數

async 函數的返回值爲 Promise 對象,async 函數返回的 Promise 的結果由函數執行的結果決定

async function fn1() {
  return 1
}

const result = fn1()
console.log(result) // Promise { 1 }

在控制檯能夠看見以下信息

既然是Promise對象,那麼咱們用 then 來調用,並拋出錯誤,執行 onRejected() 且 reason 爲錯誤信息爲「我是錯誤」

async function fn1() {
  // return 1
  // return Promise.resolve(1)
  // return Promise.reject(2)
  throw '我是錯誤'

}

fn1().then(
  value => { console.log('onResolved()', value) },
  reason => { console.log('onRejected()', reason) } // onRejected() 我是錯誤
)

5.2 await表達式

await 右側的表達式通常爲 promise 對象, 但也能夠是其它的值:

  • 若是表達式是 promise 對象, await 返回的是 promise 成功的值
  • 若是表達式是其它值, 直接將此值做爲 await 的返回值
function fn2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1000)
    }, 1000);
  })
}

function fn4() { return 6 }

async function fn3() {
  // const value = await fn2() // await 右側表達式爲Promise,獲得的結果就是Promise成功的value
  // const value = await '還能夠這樣'
  const value = await fn4()
  console.log('value', value)
}

fn3() // value 6

await 必須寫在 async 函數中, 但 async 函數中能夠沒有 await,若是 awaitPromise 失敗了, 就會拋出異常, 須要經過 try...catch 捕獲處理

function fn2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve(1000)
      reject(1000)
    }, 1000);
  })
}

async function fn3() {
  try {
    const value = await fn2()
  } catch (error) {
    console.log('獲得失敗的結果', error)
  }
}

fn3() // 獲得失敗的結果 1000

5.3 Async/await 比 Promise 更優越的表現

簡潔乾淨,使用async/await能省去寫多少行代碼

錯誤處理,async/wait 能用相同的結構和好用的經典 try/catch 處理同步和異步錯誤,錯誤堆棧能指出包含錯誤的函數。

調試,async/await 的一個極大優點是它更容易調試,使用async/ await則無需過多箭頭函數,而且能像正常的同步調用同樣直接跨過await調用。

~~完。


關注這個脫髮、擺攤、賣貨、持續學習的程序員,第一時間閱讀最新文章,會優先兩天發表新文章。關注便可領取大禮包,準能爲你節省很多錢!

image

相關文章
相關標籤/搜索