Async Generator Functions

Async functions

什麼是 Async Functions?

An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.

Async function 裡面的程式碼是非同步執行,並 implicit 的回傳一個 Promise。

async function foo() {
  return 10;
}

console.log(foo());  // Promise {<resolved>: 10}
console.log(foo().then(result => console.log(result)));  // 10

Promise? WTF?

JavaScript 是 single thread 的執行,因此要處理非同步行為需要特殊的方式。

Callback

最古老的處理非同步的方式就是 callback,給予一個非同步 function 一個 callback function 參數,當該非同步 function 執行結束時會自動呼叫提供給他的 callback function 來處理 result。

// jQuery
$.get( "ajax/test.html", function( data ) {
  console.log(data);
});

呼叫一個 request 去取得結果大部分情況下會是一個非同步的行為(你應該不會希望一個 request 讓頁面整個卡住?),jQuery 的 get function 可以接受第二個參數當作 callback function,當 request 回傳的時候,會把 result 當作參數去呼叫提供給他的 callback function,這時候你的 console.log 就能把結果印出來。

Promise

Callback 聽起來很美好,那 Promise 是用來做什麼的呢?

Callback 最大的問題在於,在巢狀的 callback 中會看起來很不直觀,例如以下的 code。

const booksOfSameWriter = [];

$.get('/api/myRentBooks', function(books) {
  if(books.bookList.length > 0) {
    const bookId = books.bookList[0];
    $.get(`/api/book/${bookId}`, function(book) {
      const writerId = book.writer.id;
      $.get(`/api/writer/${writerId}`), function(writer) {
        for (const bookIdOfWriter of writer.books) {
          $.get(`/api/book/${bookIdOfWriter}`, function(writerBook) {
            booksOfSameWriter.push(writerBook);
          })
        }
      }
    })
  }
});

而 Promise 用了另一種 syntax 來表示這種非同步的執行過程。

const booksOfSameWriter = [];

$.get('/api/myRentBooks')
 .then(function(books) {
   const bookId = books.bookList[0];
   return $.get(`/api/book/${bookId}`);
 })
 .then(function(book) {
   const writerId = book.writer.id;
   return $.get(`/api/writer/${writerId}`);
 })
 .then(function(writer) {
   for(const bookIdOfWriter of writer.books) {
     $.get(`/api/book/${bookIdOfWriter}`)
      .then(function(writerBook) {
        booksOfSameWriter.push(writerBook);
      })
   }
 });

甚至,Promise 還有很多有趣的 function 可以用,像是如果我們要等 booksOfSameWriter 都全部寫入才繼續處理,可以這樣做

$.get('/api/myRentBooks')
 .then(function(books) {
   const bookId = books.bookList[0];
   return $.get(`/api/book/${bookId}`);
 })
 .then(function(book) {
   const writerId = book.writer.id;
   return $.get(`/api/writer/${writerId}`);
 })
 .then(function(writer) {
   return Promise.all(
     writer.books.map(bookIdOfWriter => $.get(`/api/book/${bookIdOfWriter}`))
   );
 })
 .then(function(booksOfSameWriter) {
   console.log(booksOfSameWriter);
 });

是不是看起來比 callback 好用一些?

Promise 或許可以取代 callback,但很可惜的是他們並無法全自動轉換。原因在於每個 API 設計的 callback 幾乎沒有什麼規範可言,例如有些 API 的 callback 第一個參數是 error,第二個參數才是 response,所以除非要對每個 API 都去做轉換,不然沒有一個通用的方式去把所有的 callback function 自動轉換成 Promise。

對於 event 來說,目前主要習慣還是使用 callback 來處理。(或是後來崛起的 RxJS)

Async & Await

講完了 Promise,還記得一開始要介紹的是 Async functions 嗎?

Promise 雖然看起來已經很棒了,但還是不夠直觀,大家看到一堆 then 一堆 anonymous function 就頭痛,難道不能長得像同步程式一樣好看嗎?JS 規範團隊聽到了大家的心聲,所以出現了大家近期非常追捧的 async 和 await 了。

首先,async 用來標示在 function 上面,讓這個 function 隱晦的回傳一個 Promise,就算你根本就沒有寫 Promise

再來,await 只能在 async function 裡面使用,而 await 後面可以接一個 Promise 的 value,他會等待該 Promise 的值被 resolve,並把 resolve 的值(原本寫在 anonymous function 參數的值)回傳。

function normalFun() {
  return new Promise(resolve => {
    fetch('/api/test')
      .then(data => {
        console.log(data);
        resolve();
      });
  })
}

async function asyncFun() {
  const data = await fetch('/api/test');
  console.log(data);
}

沒錯,這就是 async 和 await,本質上他做的事情和 Promise 一樣,只是用一些 syntax 來讓他看起來更像同步程式一點。

Generator Functions

什麼是 Generator Functions?

Generator functions are functions that can be paused and resumed.

使用 yield 來暫停並回傳,下次呼叫 next() 則從上次暫停的地方繼續執行。

function* gen() {
  yield 1;
  yield 'yoyo';
}

const g = gen();
g.next();    // {value: 1, done: false}
g.next();    // {value: "yoyo", done: false}
g.next();    // {value: undefined, done: true}

Generator Function 回傳的其實是一個符合 Iterator 介面的 object,這個 object 可以被 for ... of loop 使用。

function* gen() {
  yield 1;
  yield 3;
  yield 5;
}

for(let i of gen()) {
  console.log(i);  // 1, 3, 5
}

Generator Function 的使用案例

而 Generator Function 到底有什麼用呢?要被 for ... of loop 使用的話,用 array 不也行嗎?

function wtf() {
  return [1, 3, 5];
}

for(let i of wtf()) {
  console.log(i);  // 1, 3, 5
}

最大的差別在於,Generator Function 並不是一次產生全部的值,而是每次呼叫去產生下一個值

所以其實你可以寫無窮迴圈也沒關係,像是這樣

function* infGen() {
  let i = 0;
  while(true) {
    yield i++;
  }
}

for(let i of infGen()) {
  console.log(i);
  if (i >= 10) {
    break;
  }
}

// should print 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

不過說是這麼說,在實際真正的 project 會用到 Generator Function 的機會少之又少,以前最常見的情況是 node 7.0 以下還不支援 async & await 的時候,使用 Generator Function 來實作。

async function foo(x) {
  let data;
  data = await x + 1;
  data = await data + 1;
  return data;
}

foo(10).then(data => console.log(data));  // should print 12

像上面的 async & await 會被 babel 轉換成

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

function foo(_x) {
  return _foo.apply(this, arguments);
}

function _foo() {
  _foo = _asyncToGenerator(function*(x) {
    let data;
    data = (yield x) + 1;
    data = (yield data) + 1;
    return data;
  });
  return _foo.apply(this, arguments);
}

foo(10).then(data => console.log(data));

簡單的來說就是利用 Generator Function 可以暫停執行的特性加上 Promise 去達到 await 的功能。

Async Generator Functions

References:

Last updated

Was this helpful?