async await 你真的用對了嗎?


大部分同學了解Promise,也知道async await可以實現同步化寫法,但實際上對一些細節沒有理解到位,就容易導致實際項目中遇到問題。
開始先拋結論,下文將針對主要問題點進行論述。
1、所有async方法調用,必須加await或catch,捕獲錯誤(等待就用await,無需等待就用catch);如果最上層的async方法是被框架(react、egret)調用的,無法加await,則需要在這個async方法內做好try catch,不要把報錯拋到框架層;
2、async方法,實際返回了一個promise,默認把return值作為promise的resolve內容,而報錯則封裝為promise的reject;
3、async方法內那么遇到異常要終止,可以直接throw ‘xxx’/Error;
4、async方法內如果有調用下一層方法(這個方法是async方法或返回Promise),則需要加await,等待這個promise結果;如果同時要返回該下層調用的return值,則可以省略await,改為直接return這個Promise(但不建議,還是統一await同步寫法比較好理解,詳見下文例子);
5、async方法如果正常執行,則直接執行完,return即可,不需要自行創建一層promise。 
 

1. 為什么async方法一定要加await或catch?

這里,需要先看一個例子,大家看看有什么問題。

main();

async function main() {
  try {
    loadImage();
    loadConfig();
  } catch (e) {
    console.log('main', e);
  }
}

function loadImage(){
  return new Promise((resolve, reject) => {
    setTimeout(reject, 1000, 'network error');
  });
}

async function loadConfig(){
  throw 'logic bug';
  await wait();
  console.log('config ok');
}

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

答案公布:

 

 

無法捕獲loadImage和loadConfig的報錯。

上述代碼是一個典型,實際是從項目某個同學代碼中抽象得來的。雖然看起來很工整很穩健,try catch做的很到位,但實際上,他沒有把async和await理解透徹,沒有理解到async返回的是Promise,無論是async內同步的報錯還是異步(延遲)的報錯,對上層調用來說,都是一個微任務。

要解決上述問題,關鍵點就是,調用loadImage和loadConfig時,加await。

async function main() {
  try {
    await loadImage();
    await loadConfig();
  } catch (e) {
    console.log('main', e);
  }
}

所以,調用async方法,不加await,就類似一個耍流氓行為,等同於使用Promise但不加catch。

另外,最頂層的方法main再被調用時,由於沒有包裹在async內,無法使用await,此時我們可以在main()后加上catch(),因為async方法實際返回的是Promise。題外話:目前top-level await還沒有正式成為標准,但最新V8引擎里邊已經可以使用(https://v8.dev/features/top-level-awaithttps://github.com/tc39/proposal-top-level-await

 

2. 為什么async方法內不要return Promise?

先看一個典型的例子

async function main() {
  try {
    const result = await load(url);
    //...
  } catch (e) {
    console.error(e);
  }
}

async function load(url) {
  if (!url) {
    return Promise.reject('url is invalid');
  } else {
    const result = await fetch(url);  //代表一個異步操作
    return Promise.resolve(result);
  }
}

大家再看看這段代碼是否有問題?

答案公布:

運行時,實際沒有問題,邏輯是正常的,也能捕獲錯誤。但是,有一些不足,多了一層Promise,會導致性能下降(新版本chrome解決了),而且影響回調執行時機。

接下來通過兩個代碼對比一下,大家會更清楚。

 

代碼片段1

console.log('script start');

async function async1() {
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 end');
}

async1();
setTimeout(function() {
  console.log('setTimeout');
}, 0);
new Promise(resolve => {
  console.log('Promise');
  resolve();
}).then(function() {
  console.log('promise end');
});
console.log('script end');

 

代碼片段2

console.log('script start');

async function async1() {
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 end');
return Promise.resolve().then(()=>{ console.log('async2 end in promise') }) } async1(); setTimeout(
function() { console.log('setTimeout'); }, 0); new Promise(resolve => { console.log('Promise'); resolve(); }).then(function() { console.log('promise end'); }); console.log('script end');

 

對比一下chrome控制台運行結果:

左(片段1)   右(片段2)

    

不同點就是,async1中await async2的時間推遲了,排在另外一個promise微任務之后。

通過這例子可見,雖然async方法里邊return一個Promise和直接return 值 並沒有明顯的差異,但會在調用時機上產生一些微妙的變化。

所以,總體來說,不建議在async方法中再return或reject一個Promise。

 

3. 參考寫法

最后,綜合上述結論,提供一些參考寫法,大家可以按需取用。

main().catch(()=>{});   // 頂層調用,如果沒有async包裹就用catch,如果是框架內調用,則在main函數體中做好catch

async function main() {
  try {
    const result = await load(url);
    //...
  } catch (e) {
    // 所有try內的async方法均有await,所有錯誤都會層層拋出,直到這里捕獲
    console.error(e);
  }
}

async function load(url) {
  if (!url) {
    throw 'url is invalid';  // 直接throw錯誤信息,簡潔明了,直接中斷后續流程
  }

  const config = await fetch(url);  // 假如fetch接口是一個網絡獲取,接收url,返回一個Promise
  return await runTask(config);  //代表一個異步操作
  // return runTask(config); // 和上一行,兩種做法都可以,這里是return語句,可以把promise當做async方法的return值,上層await會解開。但為了方便記憶,不建議使用這個方式,應該統一使用await。
}

async function runTask(data) {
  // 對接一個不支持Promise的第三方庫,我們只需要在最下層方法,包一個promise
  return new Promise((resolve, reject) => {
    thirdPartyRun(data, (res) => {
      resolve(res); // 這里返回數據
    }, (e) => {
      reject(e); // 這里可以做一些錯誤信息轉換
    });
  });
}

// 代表一個不支持Promise的第三方庫,如何對接到async await體系
function thirdPartyRun(data, success, fail) {
  //...
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM