JS Promise axios 請求結果後面的.then() 是什麼意思

Promise 是JS中一種處理異步操作的機制,在現在的前端代碼中使用頻率很高。Promise 這個詞可能有點眼生,但你肯定見過 axios.get(...).then(res => {...});用於異步請求的 axios 返回的就是一個 Promise 對象。

平時一直在代碼中 .then() .catch() 地寫來寫去,終於決定要認真學一學這個 Promise 到底是怎麼回事,希望這篇學習筆記也能幫到你。

Promise 對象

一個 Promise 對象表示一個異步操作的執行結果,包括狀態(成功/失敗)和值(成功返回值/錯誤原因)。一個 Promise 在創建的時候異步操作可能還沒執行完成,通過持有這個 Promise 對象,可以在未來異步操作完成的時候對結果進行相應操作。

Promise 對象的狀態

這裡有幾個常用的名詞,用來表示 Promise 對象當前的具體狀態:

Pending 待定:剛創建時的初始狀態,還沒有確定執行結果

Fulfilled 已兌現:異步操作執行成功,並返回一個值

Rejected 已拒絕:異步操作執行失敗,並返回一個錯誤原因

Settled 已敲定 / Resolved 已決議:“待定”狀態的反面,都表示異步操作已經執行完成,即已兌現已拒絕

回調函數

如果完全不關心異步操作的執行結果,那就把它放在那自己執行就可以瞭;但通常情況下我們總是要對操作執行的結果進行後續處理的,例如更改頁面上的數據顯示、錯誤處理等。但由於異步操作不知道什麼時候可以執行完成,就出現瞭“回調函數”的概念,意思就是等到異步操作處理結束瞭,再回過頭來調用這個函數來對執行結果進行處理。

傳統做法是,在執行異步操作的時候就把回調函數作為參數傳進去,比如最常見的:

setTimeout(function(){
    console.log("成功!");
}, 250);

setTimeout() 函數是最常見的異步函數之一,眾所周知它的作用就是在指定時間後執行指定代碼。仔細看就會發現,setTimeout()函數接收兩個參數,第二個參數是等待時間,而第一個參數就是回調函數,即等待指定的時間之後要回來調用這個函數。

很顯然這種傳參的做法有很多不方便的地方,比如把對結果的後續處理和異步操作本身耦合在瞭一起,以及著名的回調地獄:

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

Promise.then() 綁定回調函數

有瞭 Promise 之後,就能把回調和異步操作本身分開瞭。無論一個 Promise 對象當前是否已經執行完畢,我們都能在它上面綁定回調函數,並且保證回調函數被執行;這就是喜聞樂見的 then() 方法。

p.then(onFulfilled[, onRejected]);
p.then(value => {
  // fulfillment
}, reason => {
  // rejection
});

then() 方法的語法很簡單,有兩個可選參數,分別代表當 Promise 的狀態變為成功(fulfilled)和失敗(rejected)時所使用的回調函數。

如果隻想綁定 onRejected(即失敗時的錯誤處理函數),下面兩種寫法完全等價,第二種是第一種的簡寫形式。

p.then(null, failureCallback);
p.catch(failureCallback);

使用 Promise:鏈式調用

如果隻是用 then 來綁定回調函數,那並不能解決回調地獄的問題。然而很妙的地方來瞭: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);

鏈式調用的實現

能做到鏈式調用的魔法來自 then() 方法:它會在執行相應的回調函數之後,返回一個新的 Promise 對象,並且插入 Promise 鏈的當前位置

這裡稍微有點繞,容易把回調函數等同於 then() 方法本身。實際上成功/失敗的回調函數隻是 then() 的參數而已;而實際執行 then() 的時候,它會先根據 promise 的狀態調用相應的回調函數,再根據回調函數的執行結果生成一個新的 Promise 對象並返回;具體的對應規則如下:

回調函數執行情況 then() 返回的 Promise 對象
返回值 return x; fulfilled 狀態,參數為 x
直接返回 return; / 無 return 語句 fulfilled 狀態,參數為 undefined
拋出錯誤 throw err; rejected 狀態,參數為 err
返回已決議的 Promise 狀態和參數與返回的 Promise 一致
返回未定的 Promise 未定的 Promise,回調參數與返回的相同

下面這個例子中,初始 Promise 的狀態為已拒絕,然後第一個 then() 調用瞭綁定的 onRejected,返回瞭狀態為 fulfilled 的新 Promise 對象,並傳遞給瞭鏈中的下一個 then():

Promise.reject()
  .then(() => 99, () => 42) // 調用 onRejected(return 42;),表格中的第一種情況
  .then(solution => console.log('Resolved with ' + solution)); // Resolved with 42

同時,你可能還記得 then() 的參數定義,兩個回調函數都是可選的;如果沒有傳入對應的回調函數,then() 會直接把原 promise 的終態返回,不做額外處理。

錯誤處理

遇到異常拋出(被拒絕的 promise)時,瀏覽器會順著 Promise 鏈尋找下一個 onRejected 回調函數(經常被簡寫為 .catch()),並跳過中間的 onFulfilled 回調函數。這種執行邏輯與同步代碼中的 try-catch 執行過程非常相似:

// 異步 Promise
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

// 同步
try {
  let result = syncDoSomething();
  let newResult = syncDoSomethingElse(result);
  let finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch(error) {
  failureCallback(error);
}

一個具體的例子:

Promise.resolve()
.then(() => {
    throw new Error('出錯');
    console.log('a');
})
.then(() => {
    console.log('b');
})
.catch(() => {
    console.log('c');
})
.then(() => {
    console.log('d');
});

// 輸出結果:
// "c"
// "d"

常見錯誤

doSomething().then(function(result) {
  doSomethingElse(result) // 沒有返回 Promise 以及沒有必要的嵌套 Promise
  .then(newResult => doThirdThing(newResult));
}).then(() => doFourthThing());
// 最後,是沒有使用 catch 終止 Promise 調用鏈,可能導致沒有捕獲的異常

上面這個例子在 Promise 中進行瞭嵌套,但沒有將嵌套的 Promise 對象返回,因此doFourthThing() 不會等待 doSomethingElse()或 doThirdThing() 完成,而是並行運行;並且如果有傳入參數,接收到的會是 undefined 而不是 doThirdThing() 的執行結果。

正確的寫法應該是:

註:箭頭函數 () => x 是 () => { return x; } 的簡寫,即返回瞭新的 Promise 對象

doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(newResult => doThirdThing(newResult))
.then(() => doFourthThing())
.catch(error => console.log(error));

創建 Promise 對象

如果要執行的異步操作沒有返回 Promise 對象,可以用 new 和構造器創建自己的 promise。構造器的兩個參數的作用是在異步操作成功/失敗時,轉換 Promise 對象的狀態並傳遞對應參數。

const myFirstPromise = new Promise((resolve, reject) => {
  // 做一些異步操作,最終會調用下面兩者之一:
  // resolve(someValue); // fulfilled
  // reject("failure reason"); // rejected
});

// 一個例子
function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
};

Promise 其他靜態方法

創建已決議的 Promise 對象

Promise.resolve(value) 和 Promise.reject(reason) 方法分別返回一個已經成功/失敗的 Promise 對象。

const p1 = new Promise((resolve, reject) => {
  resolve();
});
const p2 = Promise.resolve();

如果 resolve(value) 的參數是帶有 then() 方法的 Promise 對象,函數將返回其自帶 then() 方法的執行結果;如果參數為空或是基本類型,返回的Promise對象與在 then() 方法中 return 對應值的結果一致,參見上文表格。基於這樣的特性, resolve(value) 方法可以用於將不確定是否為 Promise 對象的 value 值統一為 Promise。

多個 Promise 對象

Promise.all(iterable)

  • 參數列表中的所有的 promises 都成功時,返回一個 fulfilled 的 Promise 對象,參數值是所有 promises 成功返回值的列表(順序不變)
  • 如果任何一個 promise 失敗,立即返回一個 rejected 的 Promise 對象,參數是這個失敗 promise 的錯誤信息
Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });

Promise.allSettled(iterable)列表中所有 promises 都已敲定後返回一個promise,並帶有一個對象數組,對應每個promise 的結果。

Promise.any(iterable)當列表中的任意一個 promise 成功時,立即返回這個 promise 的值。

Promise.race(iterable)當列表中任意一個 promise 成功或失敗時,立即返回該 promise 對象的執行結果。

一個綜合例子(使用 setTimeout 模擬異步操作):

// 創造一個狀態為 fulfilled,參數為"foo"的 Promise 對象
Promise.resolve("foo")
  .then(function(string) {	// string: "foo"
    // 返回狀態為 fulfilled,參數為"foobar"的對象
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        string += 'bar';
        resolve(string);
      }, 1);
    });
  })
  .then(function(string) {	// string: "foobar"
    setTimeout(function() {
      string += 'baz';
      console.log(string);
    }, 1)
    // 返回值"foobar"
    return string;
  })
  .then(function(string) {	// string: "foobar"
    console.log("Last Then");
    console.log(string);
  });

// 輸出結果:
// Last Then
// foobar
// foobarbaz(由第二個 then 中的 setTimeout 輸出)

結語&參考文獻

以上是閱讀學習瞭 MDN 文檔後個人總結的學習筆記,可能存在錯誤和疏漏,歡迎指正與討論!

MDN-Promise

MDN-使用Promise

MDN-Promise.then()

到此這篇關於JS Promise axios 請求結果後面的.then() 是什麼意思 的文章就介紹到這瞭,更多相關JS Promise axios  .then() 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: