理解ES7中的async/await

龍恩0707發表於2017-08-12

理解ES7中的async/await

   優勢是:就是解決多層非同步回撥的巢狀

 從字面上理解 async/await, async是 "非同步"的含義,await可以認為是 async wait的簡寫,因此可以理解 async 用於宣告一個function是非同步的,而await用於等待一個非同步方法執行完成返回的值(返回值可以是一個Promise物件或普通返回的值)。
注意:await 只能出現在 async函式中。

1-1 async的作用?
首先來理解async函式是怎麼處理返回值的,我們以前寫程式碼都是通過return語句返回我們想要的值,但是async與return有什麼區別呢? 下面我們看如下程式碼:

async function testAsync() {
  return 'hello world';
}
const result = testAsync();
console.log(result);  // 列印出來看到 返回一個promise物件

如上程式碼,我們輸出的是一個Promise物件。因此 async返回的是一個Promise物件,因此我們可以使用 then()鏈來處理這個Promise物件。像如下程式碼:

async function testAsync() {
  return 'hello world';
}
const result = testAsync();
result.then(v => {
  console.log(v);  // 輸出 hello world
});

但是如果 async函式沒有返回值的話,那麼就會返回 undefined; 如下程式碼:

async function testAsync() {
  return;
}
const result = testAsync();
result.then(v => {
  console.log(v);  // 輸出 undefined
});

1-2 await的作用?
從語義上說,await是在等待一個async函式完成的,async函式返回的是一個Promise物件,await等待的是一個表示式,這個表示式的計算結果是Promise物件或其他值。
async函式返回一個Promise物件,await作用是用於等待一個async函式的返回值。
await函式後面可以接普通函式呼叫或直接量,請看如下程式碼:

function getSomething() {
  return 'something';
}

async function testAsync() {
  return Promise.resolve('hello');
}

async function test () {
  const v1 = await getSomething();
  const v2 = await testAsync();
  console.log(v1);  // something
  console.log(v2);  // hello
}
test();

注意:await 是等待async函式返回的Promise物件或其他值,await是一個運算子,用於組成表示式, 如果等待的是一個Promise物件,await會阻塞後面的程式碼(async呼叫不會造成堵塞,它內部所有的堵塞
都被封裝在一個Promise物件中非同步執行),等待Promise物件的resolve,然後得到resolve的值,作為await表示式的運輸結果。

1-3 async/await 的一起使用的作用及優勢在哪?
我們先不用 async/await, 來看看使用setTimeout模擬非同步操作如下程式碼:

var test = function(time) {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      resolve('hello world');
    }, time)
  });
};

test(1000).then((v) => {
  console.log(v);
});

如果改用 async/await 程式碼如下:

var testAsync = function(time) {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      resolve('hello world');
    }, time)
  });
};

async function test(time) {
  const v = await testAsync(time);
  console.log(v); // hello world
}
test(1000);

看上面的程式碼,反而會覺得 使用 async/await 程式碼變得多一點,複雜一點,但是結果貌似都一樣,那麼使用 async/await的優勢在哪?

1-4 async/await的優勢在於處理then鏈

單一的Promise鏈貌似不能發現 async/await的優勢,但是如果需要處理多個Promise組成的then鏈的時候,優勢可以看出來,因為Promise是通過then鏈來解決多層回撥的問題,現在我們又可以使用
async/await來進一步優化,他們的優點就是解決多層非同步回撥的巢狀。

假設我們現在有一個需求是,分多個步驟完成,每個步驟都是非同步的,並且後面的非同步都需要依賴於上一個非同步回撥返回的資料,進行往下傳遞。我們先用 setTimeout來模擬非同步操作。

function useTimeout (t) {
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      resolve(t+100)
    }, t);
  });
}

function step1(t) {
  console.log(`step1 with ${t}`); // step1 with 300
  return useTimeout(t);
}

function step2(t) {
  console.log(`step2 with ${t}`); // step2 with 400
  return useTimeout(t);
}

function step3(t) {
  console.log(`step3 with ${t}`); // step3 with 500
  return useTimeout(t);
}

function test() {
  const time1 = 300;
  step1(time1)
    .then(time2 => step2(time2))
    .then(time3 => step3(time3))
    .then(res => {
      console.log(`result is ${res}`);  // result is 600
    })
} 
test();

如果我們使用 async/await來實現,程式碼變為如下:

function useTimeout (t) {
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      resolve(t+100)
    }, t);
  });
}

function step1(t) {
  console.log(`step1 with ${t}`); // step1 with 300
  return useTimeout(t);
}

function step2(t) {
  console.log(`step2 with ${t}`); // step2 with 400
  return useTimeout(t);
}

function step3(t) {
  console.log(`step3 with ${t}`); // step3 with 500
  return useTimeout(t);
}

async function test() {
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time2);
  const result = await step3(time3);
  console.log(`result is ${result}`);
} 
test();

上面我們可以看到 使用async/await 程式碼看起來都是同步的,等第一步完成後,再執行第二步,依次類推..... 並且不需要更多的回撥函式巢狀。

下面我們再來看下我們之前講的,後面的步驟需要上一個步驟的結果傳遞進去,使用async/await的優勢可能更明顯。
如下程式碼:

function useTimeout (t) {
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      resolve(t+100)
    }, t);
  });
}

function step1(t1) {
  console.log(`step1 with ${t1}`); // step1 with 300
  return useTimeout(t1);
}

function step2(t1, t2) {
  console.log(`step2 with ${t1} and ${t2}`); // step2 with 300 and 400
  return useTimeout(t1+t2);
}

function step3(t1, t2, t3) {
  console.log(`step3 with ${t1} and ${t2} and ${t3}`); // step3 with 300 and 400 and 800
  return useTimeout(t1+t2+t3);
}

async function test() {
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time1, time2);
  const result = await step3(time1, time2, time3);
  console.log(`result is ${result}`);  // result is 1600
} 
test();

1-5 捕捉錯誤
我們可以使用 try catch 來捕獲錯誤,如下程式碼:

var sleep = function (time) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      // 出錯了, 返回 error
      reject('error');
    }, time);
  })
};

var start = async function() {
  try {
    console.log('start');
    await sleep(3000); // 返回一個錯誤
    // 下面程式碼不會被執行
    console.log('end');
  } catch (err) {
    console.log(err); // 捕捉錯誤 error 
  }
};

start();

相關文章