Promise 的出現主要是為了解決 JavaScript 中異步操作的處理問題。在傳統的 JavaScript 中,異步操作通常使用回調函數來處理,但這種方式會導致代碼嵌套層級過深,可讀性差,而且容易產生回調地獄(callback hell)的情況,降低了代碼的可維護性和可理解性。

簡單的説法:它就像是一種特殊的承諾,用來解決 JavaScript 中的異步問題, 想象一下你承諾要跟你朋友五一去北京玩,但是時間還沒到。在等待這個過程完成之前,你可以做其他事情,一旦約定的時間到了,你就要履行承諾,如果你去了就履行,但是你也有權利不履行。

Promise解決什麼問題

回調地獄:如果有多個異步操作依賴於前一個操作的結果,代碼就會出現多層嵌套的回調,造成代碼結構混亂難以維護。

 asyncOperation1(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作1完成');
      callback();
    }, 1000);
  }

  asyncOperation2(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作2完成');
      callback();
    }, 1500);
  }

  asyncOperation3(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作3完成');
      callback();
    }, 2000);
  }

  ngOnInit(): void {
    console.log('開始');
    this.asyncOperation1(() => {
      this.asyncOperation2(() => {
        this.asyncOperation3(() => {
          console.log('所有異步操作完成');
        });
      });
    });
}
開始 異步操作1完成 異步操作2完成 異步操作3完成 所有異步操作完成

錯誤處理困難:錯誤處理通常依賴於回調函數的調用方式,容易出現漏處理或混亂的情況。

asyncOperation(callback: (error: any, result?: number) => void) {
    setTimeout( () => {
      const randomNumber = Math.random();
      if (randomNumber < 0.5) {
        callback(null, randomNumber);
      } else {
        callback(new Error('異步操作失敗'));
      }
    }, 1000);
  }

  this.asyncOperation((error: any, result?: number) => {
      if (error) {
        console.error('異步操作失敗:', error.message);
        // 漏處理錯誤,沒有提供錯誤處理邏輯
      } else {
        console.log('異步操作成功,結果為:', result);
        // 只處理了成功情況,沒有考慮錯誤處理的可能性
      }
    });
小於0.5 異步操作失敗: 異步操作失敗
 大於0.5 異步操作成功: 0.52312344 (隨機)

Promise基本使用

Promise 對象通過自身的狀態,來控制異步操作。Promise 實例具有三種狀態。

  • 待定(pending):初始狀態,既沒有被兑現,也沒有被拒絕。
  • 已兑現(fulfilled):意味着操作成功完成。
  • 已拒絕(rejected):意味着操作失敗。

上面三種狀態裏面,fulfilled和rejected合在一起稱為resolved(已定型)。

狀態的變化只有2種

  • 從“未完成”到“成功”
  • 從“未完成”到“失敗”

狀態一旦發生變化,就不會再發生變化,因此,Promise只有2種結果

  • 異步操作成功:從pending到fulfilled
  • 異步操作失敗:從pending到rejected

Promise 構造函數

Promise 構造函數是用來創建 Promise 實例的基礎。它接受一個帶有兩個參數的函數作為參數。這個函數會立即執行,通常包含異步操作。這兩個參數一般被稱為 resolve 和 reject,它們是 JavaScript 引擎提供的回調函數。resolve 函數用於表示異步操作成功並返回結果,而 reject 函數用於表示異步操作失敗並返回一個錯誤對象。

const promise = new Promise((resolve, reject) => {
  const randomNumber = Math.random();

  if (randomNumber < 0.5){
    resolve(randomNumber);
  } else { /* 異步操作失敗 */
    reject(new Error(異步操作失敗));
  }
});

Promise 的鏈式調用

.then() 方法最多接受兩個參數;第一個參數是 Promise 兑現時的回調函數,第二個參數是 Promise 拒絕時的回調函數。每個 .then() 返回一個新生成的 Promise 對象,這個對象可被用於鏈式調用,

 const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("yunzhi");
      }, 300);
    });

    promise
      .then((value) => {
        console.log(value, '異步操作')
      }, (value) => {
        console.log(value, '操作失敗')
      });
     // yunzhi 操作成功

   promise
        .then((value) => {
          console.log(value, '操作成功');
          return value + '.club';
        }, (value) => {
          console.log(value, '操作失敗')
        }).then(value => {
        console.log(value, '操作成功');
      });

  // yunzhi 操作成功
  // yunzhi.club 操作成功

解決回調地獄

Promise 的鏈式調用可以使代碼更加扁平化、易於理解,並且避免了多層嵌套的回調函數,從而解決了回調地獄問題。

asyncOperation1(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作1完成');
      callback();
    }, 1000);
  }

  asyncOperation2(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作2完成');
      callback();
    }, 1500);
  }

  asyncOperation3(callback: () => void) {
    setTimeout(function () {
      console.log('異步操作3完成');
      callback();
    }, 2000);
  }

asyncOperation1()
    .then(() => {
        return asyncOperation2();
    })
    .then(() => {
        return asyncOperation3();
    })
    .then(() => {
        console.log('所有異步操作完成');
    });
開始 異步操作1完成 異步操作2完成 異步操作3完成 所有異步操作完成

Promise 的鏈式調用和 .catch() 方法來更可靠地處理錯誤,漏處理或混亂的情況

 this.asyncOperation().then((result) => {
      console.log('異步操作成功,結果為:', result);
    }).catch((error) => {
      console.error('異步操作失敗:', error.message); 
     // 在 .catch() 中處理異步操作失敗的情況
    })

asyncOperation() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
          const randomNumber = Math.random();
          if (randomNumber < 0.5) {
            resolve(randomNumber);
          }
          reject(new Error('異步操作1失敗'));
        }
        , 1000)
    })
  }
小於0.5 異步操作失敗: 異步操作失敗
 大於0.5 異步操作成功: 0.52312344 (隨機)

總結

通過 Promise 構造函數,我們可以更加靈活地處理異步操作,並且避免了傳統回調函數所帶來的回調地獄問題。更好的錯誤處理: Promise 提供了統一的錯誤處理機制,使得錯誤更容易捕獲和處理。通過 .catch() 方法,可以捕獲到 Promise 鏈中的任何一個 Promise 對象的錯誤。