async/await 處理異步請求,能不能這麼玩?

Async/await 是 ES7 中的新特性,相信你們或多或少的已經應用在本身的項目或 Demo 中,它可使異步代碼像同步代碼同樣,配合 Promise 解決了回調地獄的問題,的確它給咱們帶來了不少方便的地方,可是在 async/await 中如何來處理錯誤呢?javascript

在 async/await 中處理錯誤

在發送異步請求時,咱們不免會遇到請求錯誤,在 Promise 中咱們使用 .catch() 捕獲錯誤,在 async/await 中,相信你們都會答得出使用 try/catch 來捕獲錯誤。java

可是...git

有沒有跟我同樣的人,儘可能不容許代碼中出現 try/catch ?有種污染代碼的感受。github

await-to-js 帶來的靈感

await-to-js 經過對 Promise 進行了一層封裝,返回一個數組 [err, data],await 執行函數能夠等待異步方法的結束,err 若是不爲 null,則判斷爲捕獲異常。他的代碼其實很是少,使用 ts 寫的,可是就是這麼簡單的代碼,卻起到了大做用。typescript

源碼:數組

export function to<T, U = any>( promise: Promise<T>, errorExt?: object ): Promise<[U | null, T | undefined]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>(err => {
      if (errorExt) {
        Object.assign(err, errorExt)
      }

      return [err, undefined]
    })
}

export default to
複製代碼

Demo:promise

import to from 'await-to-js';
async function asyncFunctionWithThrow() {
  const [err, user] = await to(UserModel.findById(1));
  if (!user) throw new Error('User not found');
}
複製代碼

擴展

有了 await-to-js 的靈感,我打算用這種方式,來解決一直困擾個人4個場景。markdown

  • 按序執行(demoTo)
  • 所有執行完畢或出現一條報錯當即繼續其餘代碼 (demoAll)
  • 有一條成功或失敗當即繼續其餘代碼 (demoRace)
  • 不論成功失敗,所有執行完畢後繼續其餘代碼 (demoAllSettled)

解決這 4 個問題場景的函數:異步

  • 參數 request: 一個 Promise 對象
  • 參數 requests: 一個 Promise 對象數組
function to (request) {
  return request.then((res) => [null, res]).catch(err => [err, null]);
}
function all (requests) {
  return Promise.all(requests).then((values) => [null, values]).catch(err => [err, null]);
}
function race (requests) {
  return Promise.race(requests).then((value) => [null, value]).catch(err => [err, null]);
}
function allSettled (requests) {
  return Promise.allSettled(requests);
}
複製代碼

使用jsfiddle演示代碼,打開控制檯看輸出結果,驗證這 4 個函數的妙用:async

Demo 說明:

假設有三臺設備須要請求他們的狀態,這裏每一個設備請求所須要的時間不盡相同(使用 setTimeout 模擬),則會出現上述的 4 中應用場景。

  • DeviceA (請求時間1秒,請求成功)
  • DeviceB (請求時間2秒,請求失敗或成功)
  • DeviceC (請求時間3秒,請求成功)
// 時間
let time = 0;
setInterval(() => time++, 1000);

function getDeviceAStatus() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('DeviceA: on');
    }, 1000);
  });
}
function getDeviceBStatus() {
  // 請求失敗
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Connection fail'));
    }, 2000);
  });
  // 請求成功
  // return new Promise(resolve => {
  // setTimeout(() => {
  // resolve('DeviceB: on');
  // }, 2000);
  // });
}
function getDeviceCStatus() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('DeviceC: on');
    }, 3000);
  });
}

async function demoTo () {
  console.log('-------------------------');
  console.log(`【 Time: ${time}s 】 Demo to begin`);
  let err, res1, res2, res3;
  [err, res1] = await to(getDeviceAStatus());
  if (err) {
    console.log(`【 Time: ${time}s 】 ${err}`);
  } else {
    console.log(`【 Time: ${time}s 】 Data: ${res1}`);
  }
  [err, res2] = await to(getDeviceBStatus());
  if (err) {
    console.log(`【 Time: ${time}s 】 ${err}`);
  } else {
    console.log(`【 Time: ${time}s 】 Data: ${res2}`);
  }
  [err, res3] = await to(getDeviceCStatus());
  if (err) {
    console.log(`【 Time: ${time}s 】 ${err}`);
  } else {
    console.log(`【 Time: ${time}s 】 Data: ${res3}`);
  }
  console.log(`【 Time: ${time}s 】 Demo to end`);
  return Promise.resolve();
}

async function demoAll () {
  console.log('-------------------------');
  console.log(`【 Time: ${time}s 】 Demo all begin`);
  const [err, values] = await all([getDeviceAStatus(), getDeviceBStatus(), getDeviceCStatus()]);
  if (err) {
    console.log(`【 Time: ${time}s 】 ${err}`);
  } else {
    console.log(`【 Time: ${time}s 】 Data: `, values);
  }
  console.log(`【 Time: ${time}s 】 Demo all end`);
  return Promise.resolve();
}

async function demoRace () {
  console.log('-------------------------');
  console.log(`【 Time: ${time}s 】 Demo race begin`);
  const [err, value] = await race([getDeviceAStatus(), getDeviceBStatus(), getDeviceCStatus()]);
  if (err) {
    console.log(`【 Time: ${time}s 】 ${err}`);
  } else {
    console.log(`【 Time: ${time}s 】 Data: ${value}`);
  }
  console.log(`【 Time: ${time}s 】 Demo race end`);
  return Promise.resolve();
}

async function demoAllSettled () {
  console.log('-------------------------');
  console.log(`【 Time: ${time}s 】 Demo allSettled begin`);
  const values = await allSettled([getDeviceAStatus(), getDeviceBStatus(), getDeviceCStatus()]);
  console.log(`【 Time: ${time}s 】 Data: `, values);
  console.log(`【 Time: ${time}s 】 Demo allSettled end`);
  return Promise.resolve();
}

(async function () {
  await demoTo(); time = 0;
  await demoAll(); time = 0;
  await demoRace(); time = 0;
  await demoAllSettled();
})();
複製代碼

請求 DeviceB 失敗的狀況:

請求 DeviceB 成功的狀況:

運行結束後你會發現,所有按照意願去執行了,代碼看起來也十分簡潔。

相關文章
相關標籤/搜索