async await promise try...catch

簡單介紹下這幾個的關系
為方便起見 用以下代碼為例簡單介紹下這幾個東西的關系,

async function buildData(name) {
      try {
        let response1 = await axios.get('/api/user?name=' + name);
        let userInfo = response1.data;
        
        let response2 = await axios.get('/api/topics?user_id' + userInfo._id);
        let posts = response2.data;
        // i got it.
      } catch(err) {
        console.log(err);
      }  
    }
    
    buildData('xiaoming');

async

在函數聲明前使用async關鍵詞修飾 說明函數中有異步操作

await

等待 后面的代碼執(zhí)行完畢 再繼續(xù)向下執(zhí)行

promise

Promise 是一個對象,從它可以獲取異步操作的消息,知道異步函數是完成了還是出錯了。
axios返回的結果就是一個promise

try catch

try catch JavaScript的異常捕獲機制,凡是在try語句塊中的代碼出錯了,都會被catch捕獲。

上面的代碼就是說

  1. buildData 這個函數被 async 修飾 說函數中有異步操作
  2. await 等待異步操作結果
  3. 如果有錯誤發(fā)生 使用try catch 捕獲異常

上面說的太過簡單,簡要的說明下 各個東西是干啥的。這里的核心是 promise 下面會逐個介紹

Promise


Promise字面上講,是一個承諾。這個承諾有三個狀態(tài)

  • pending 進行中(懸而未決)
  • fulfilled 已成功(以滿足)
  • rejected 已失敗(已拒絕)

從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理。

Promise對象有以下兩個特點:

  1. 對象的狀態(tài)不受外界的影響。只有異步操作的結果可以決定當前是哪種狀態(tài)。
  2. 狀態(tài)一旦改變 就不會再變,任何時候都可以得到這個結果。promise對象狀態(tài)的改變只有兩種情況,
    • pending 到 fulfilled
    • pending 到 rejected

只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再改變了,會一直保持這個結果,這時就定型了 resolved。
如果改變已經發(fā)生了,任何時候添加回調函數,得到的都是這個結果。

因為創(chuàng)建Promise對象時,回調函數中有resolve和reject兩個參數,后續(xù)的resolved統(tǒng)一指的是fulfilled狀態(tài),不包括rejected狀態(tài)

一個Promise對象一旦狀態(tài)確定了,它的使命也就結束了。后面的代碼都不應該再執(zhí)行了,最好return resolve();
如果狀態(tài)已經resolve了,再在后面拋出錯誤也是無效的,也不會改變狀態(tài)為rejected,后面有異常也不會拋出。

Promise對象如何知道異步操作結果又如何傳遞


Promise對象如何知道異步操作的結果呢,那就是回調函數了,一個表示成功resolve,一個表示失敗reject,

ES6 規(guī)定,Promise對象是一個構造函數,用來生成Promise實例。
下面代碼創(chuàng)造了一個Promise實例。

var promise = new Promise(function(resolve, reject) {
    // ... some code
    console.log(我一創(chuàng)建就執(zhí)行了)
    if (/* 異步操作成功 */){
      resolve(value);
    } else {
      reject(error);
    }
  });

Promise新建后,就會立即執(zhí)行,返回一個Promise對象。
Promise構造函數需要一個函數作為參數,這個函數有兩個參數,分別是resolve和reject,
它們是兩個函數,由 JavaScript 引擎提供,不用自己部署

  • resolve: 把Promise狀態(tài) 從 pending 變?yōu)?resolved ,在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去
  • reject: 把Promise狀態(tài) 從 pending 變?yōu)?reject ,在異步操作失敗時調用,并把錯誤作為參數傳遞出去
    此外,reject方法的作用,也等同于拋出異常。reject的參數會被catch捕獲。即便沒有調用reject,如果執(zhí)行過程中出錯了,也會被catch捕獲。

所以:

  1. 異步操作成功時,把結果告訴resolve回調函數 異步操作失敗 把錯誤告訴reject回調函數
  2. resolve 和 reject 回調函數 還有一個作用就是把 結果傳遞出去

Promise對象如何使用異步函數的執(zhí)行結果


通過then方法獲取異步操作的結果。
Promise實例有兩個方法:

  • then 指定了resolve狀態(tài)和reject的回調函數
  • catch 專門用來捕獲Promise對象產生的錯誤

then和catch方法返回的是一個新的Promise對象,因為Promise對象具有then和catch方法,所以可以一直.then和.catch下去
axios返回的也是Promise對象,這也是為什么axios可以那么鏈式操作了

then方法的作用就是干這個

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法的參數是兩個回調函數,都接受Promise對象傳出的值作為參數:

  • 第一個參數是resolve狀態(tài)的回調函數
  • 第二個參數是reject的回調函數, 這個參數是可選的

通常這個參數也不寫,因為Promise實例還有一個方法叫catch是專門用來捕獲異常的。

catch


catch 是 .then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調函數。

一旦catch前面的任何一個Promise發(fā)生異常,都會被catch捕獲,包括Promise函數創(chuàng)建的Promise,還有.then返回的Promise,甚至catch前面如果還有一個catch在這個catch拋出的異常也會被后一個catch捕獲。

也就是說:
Promise對象的錯誤具有冒泡性質,會一直向后傳遞,直到被捕獲為止,也即是說,錯誤總會被下一個catch語句捕獲。

所以,既然這個catch這么厲害,then函數中的第二個參數常常被省略了,然后被這個catch方法替代。

所以通常這么寫:
promise.then().catch()
promise.then().then().catch()
promise.then().then().catch().then().catch()

所以下面例子第二種寫法好些。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

一般總是建議,Promise 對象后面要跟catch方法,這樣可以處理 Promise 內部發(fā)生的錯誤。catch方法返回的還是一個 Promise 對象,因此后面還可以接著調用then方法和catch方法。

Promise 還有兩個常用方法:

  • Promise.all() :將多個Promise實例,包裝成一個新的Promise實例。內部所有的Promise的狀態(tài)都變成fulfilled,這個Promise狀態(tài)才會變成fulfilled,返回值是一個數組,但是只要有一個 rejected 這個Promise對象就會變成rejected 返回第一個被reject的實例的返回值
  • Promise.race():跟all方法一樣,只是race就想是賽跑,誰先有結果,返回誰的結果。不會等到所有的Promise都執(zhí)行完。

現有對象轉為Promise對象

Promise.resolve方法就起到這個作用

Promise.resolve('foo')
// 等價于
nnew Promise(function (resolve) {
    resolve('foo')
})

Promise.resolve方法的參數分成四種情況:

  • 參數是一個Promise實例 :不做任何修改、原封不動地返回這個實例。
  • 參數是一個thenable對象 :將這個對象轉換為Promise對象,然后立刻執(zhí)行thenable的then方法
  • 參數不是具有then方法的對象,或根本就不是對象 :反回一個新的Promise對象,狀態(tài)為resolved。
  • 不帶有任何參數: 直接返回一個resolved狀態(tài)的Promise對象
    立即resolved的對象,是在本輪事件循環(huán)結束時,而不是在下一輪循環(huán)時間開始時
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

上面代碼中,setTimeout(fn,0),在下一輪循環(huán)事件開始執(zhí)行
Promise.resolve()在本輪事件循環(huán)結束時執(zhí)行
console.log('one')立刻執(zhí)行,
因此上面的打印順序是 one two three

直接返回一個狀態(tài)為rejected Promise對象


Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態(tài)為rejected。

  var p = Promise.reject('出錯了');
    //等價于
  new Promise(function (resolve,reject) {
      reject('出錯了');
  })

Promise.reject()方法的參數,會原封不動地作為reject的理由,變成后續(xù)方法的參數。不會像Promise.resolve那樣,根據不同的情況包裝Promise

async和await


async函數是Generator函數的語法糖,將Generator的星號換成async 將yield換成await

Generator是一個狀態(tài)機,封裝了多個內部狀態(tài),執(zhí)行 Generator 函數會返回一個遍歷器對象,返回的遍歷器對象,可以使用next依次遍歷 Generator 函數內部的每一個狀態(tài)。Generator 函數是分段執(zhí)行的,yield表達式是暫停執(zhí)行的標記,而next方法可以恢復執(zhí)行。Generator 函數的執(zhí)行必須靠執(zhí)行器。

async函數比Generator函數更好用

  • 自帶執(zhí)行器,執(zhí)行起來,跟調用普通函數一樣
  • async和await 語義更清晰,async表示函數里有異步操作,await 表示緊跟在后面的表達式需要等待結果
  • await后面啥都可以跟,可以是Promise 也可以是對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作)
  • async函數的 返回值是 Promise

正常情況下,await命令是個Promise對象,如果不是 會被轉成一個 立即 resolved的對象
async函數完全可以看作多個異步操作,包裝成的一個 Promise 對象(因為await 函數返回的是Promise對象),而await命令就是內部then命令的語法糖。

然而,然而,我們沒寫錯誤處理。

async function f() {
  return 'hello world';
}
f().then().catch()

正常情況下 async 函數中return結果會使Promise對象變?yōu)?resolved狀態(tài),返回值作為then方法回調函數的參數,而出錯則會使Promise對象的變?yōu)閞eject狀態(tài),錯誤會被catch捕獲。

因為 async函數 相當于對 多個Promise的封裝,所以必須等到內部所有的await命令執(zhí)行完,才會改變自己的狀態(tài)為resolved,除非 碰到return語句或者拋出了異常。
也就是說,正常情況下 只有async函數內部的異步操作執(zhí)行完,才會執(zhí)行then后面的語句。

只要一個await后面的Promise變?yōu)閞ejected,整個async函數就會中斷執(zhí)行,整個async返回的Promise對象就會是rejected狀態(tài)

async function f() {
  await Promise.reject('出錯了');
  await Promise.resolve('hello world'); // 不會執(zhí)行
}

因為第一個await后面的對象reject了,所以整個async函數就中斷執(zhí)行了

有時,我們希望即使前一個異步操作失敗,也不要中斷后面的異步操作。
這時可以將第一個await放在try...catch結構里面,這樣不管這個異步操作是否成功,第二個await都會執(zhí)行。

await命令后面的Promise對象,運行結果可能是rejected,所以最好把await命令放在try...catch代碼塊中。

try catch


try catch是JavaScript的異常處理機制,把可能出錯的代碼放在try語句塊中,如果出錯了,就會被catch捕獲來處理異常。如果不catch 一旦出錯就會造成程序崩潰。

如果有多個await命令,可以將其都放在try catch結構中,如果執(zhí)行出錯,catch會去捕獲異常

async function f() {
    try {
     await Promise.reject('出錯了');
     console.log('上面已經出錯了');
     return await Promise.resolve('hello world');
    } catch(e) {
        console.log(e);
    }
  }
  
  f()
  .then(v => console.log(v))

catch會去捕獲try代碼塊中的錯誤,只要有一個拋出了異常,就不會繼續(xù)執(zhí)行,所以上面的代碼不會打印上面已經出錯了也不會執(zhí)行return await Promise.resolve('hello world');
因為使用了trycatch 所以 async 是順利執(zhí)行完成的,其中的報錯 被 try catch處理了,所以異常不會被async返回的Promise的catch捕獲,因此async返回的Promise對象狀態(tài)是resolved。

如果異步函數沒有依賴關系,最好并發(fā)執(zhí)行


await 會等待后面的異步操作執(zhí)行完畢,才會繼續(xù)執(zhí)行

let foo = await getFoo();
let bar = await getBar();

上面的代碼會順序執(zhí)行,
如果需要多個await沒有相互依賴,最好讓他們同時觸發(fā),可以使用以下兩種方式:

  • 使用Promise.all() 包裝一個新的Promise對象
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  • 不等待分別執(zhí)行,返回新的Promise對象
//沒用await 立即執(zhí)行返回 Promise對象
let fooPromise = getFoo();
let barPromise = getBar();
// 等待 Promise對象的結果 之前也說過 await就像是then的語法糖
let foo = await fooPromise;
let bar = await barPromise;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,410評論 5 22
  • Promiese 簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,語法上說,Pr...
    雨飛飛雨閱讀 3,492評論 0 19
  • 00、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調函數和事件——更合理和更強大。它由社區(qū)...
    夜幕小草閱讀 2,230評論 0 12
  • 本文適用的讀者 本文寫給有一定Promise使用經驗的人,如果你還沒有使用過Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,462評論 6 19
  • 《奇葩說》有一期“不給別人添麻煩是不是一種美德”,蔡康永有一段話很觸動我,大意是:麻不麻煩跟彼此之間的關系有關,關...
    彭焉閱讀 1,044評論 2 9

友情鏈接更多精彩內容