JavaScriptの非同期関数（async function）について part1

# 初めに
もともと`Symbol.iterator`と`Symbol.asyncIterator`の勉強メモをまとめながら調べる資料から`generator`の非同期関数のキーワードが出てきて、それで初めて`Thunk`と`co`を触れました。読めば読むほど`async function`の概念をたどり着く前にまず`callback`⇒`generator`⇒`Thunk`&`co`⇒`async function`の流れでメモをまとめていきたいと思います。

結論として、
- `callback`は非同期操作のための関数内関数。
- `generator`は`yield`によりコールバック地獄回避のための手動ジェネレータ。
- `Thunk`はコールバック地獄回避のための自動ジェネレータ。
- `co`は自動ジェネレータでありながら返り値が`Promise`の保証を取り入れた関数。
- `async function`は非同期操作や可読性をさらに向上させるため`async`と`await`が導入され、返り値が`Promise`の保証をする関数です。

（ブラウザ`Event Loop`では非同期関数への処理様々です。例えば`Chromium`では`Macrotask`と`Microtask`に分けられて優先度づけられている。ここの非同期関数はあくまでも`Microtask`で処理される`Promise`に指し、`Macrotask`での非同期API`setTimeout/setInterval`、`ajax`などとは無関係です。）

`co`の段階から`Promise`の使用が強調されるというのは、ES6`Promise`の出現とブラウザ側の処理と関わっているのですが。そしてこのような分け方では大事な中身の書き方が決して決まっていません。`callback`ではとうぜん`Promise`を取り入れても問題ないです。

`Thunk`と`co`は外部モジュールとして長い間使われてきましたが、`generator`、`async function`はJavaScript自体のものとして実装されています。`callback`は技法です。

...というわけでこれらの勉強メモをまとめるのが今回のメインです。そこから学んだ概念や知識を自分の言葉でまとめることによって、正しく理解したのを検証したいと思います。

https://juejin.cn/post/6844904133577670664

https://es6.ruanyifeng.com/?search=asyncIterator&x=2&y=15#docs/async

https://es6.ruanyifeng.com/?search=asyncIterator&x=2&y=15#docs/generator-async

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

https://jsprimer.net/basic/async/

# generator function vs. async function
ここの`generator`関数は普通の`generator`ではなく、`Promise`を返す非同期`generator`を指しています。

以下は`fs`を例にして非同期`generator`と`async function`両者の書き方を比べたのですが、
`function* ()`⇒`async function ()`、`yield`⇒`await`と置き換えても何もおかしくなく、同じく非同期に動作してくれるのです。

ただ`async`と`await`のほうが可読性が高くなり、`await`は`yield`より受け入れられるデータタイプが幅広く、`Promise`以外の`String`、`Number`、`Boolean`は自動的に`resolved`状態の`Promise`として返すおかげで、`async`関数は最終的に`Promise`を返すのが保証される。
```jsx
const fs = require('fs');
// note: require() is synchronous

const readFile = function (fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (error, data) => {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

// generator function
const gen = function* () {
  const f1 = yield readFile('/directory/file1');
  const f2 = yield readFile('/directory/file2');
  console.log(f1.toString());
  console.log(f2.toString());
};
// note: generator's pointer always comes one after another,
// so it works synchronously

// async function
const asyncReadFile = async function () {
  const f1 = await readFile('/directory/file1');
  const f2 = await readFile('/directory/file2');
  console.log(f1.toString());
  console.log(f2.toString());
};
```
（`require`はモジュール導入の同期操作の一つで、後では`await`でモジュール導入の非同期操作ではまとめて説明します。）

# `callback` ⇒ `generator`
なぜ`generator`関数で非同期操作が求められるのか、というのが今回のトピックに触れるとき一番の素朴な疑問です。

それまでに`generator`はイテレータにより`yield`から値を生成させるコルーチン（coroutine）だと認識して、ほかの勉強メモでは何度も練習してきましたが。

JavaScriptの非同期操作や処理だと`callback`と`Promise`に基づいた`async function`に認識していたので、同期だと思ったジェネレータでは非同期に操作できること、そして`async`と`await`キーワードが生まれるまで一連の発展がわかりませんでした。

なのでまずコールバック地獄を改善するための`generator`関数までまとめたいです。
```jsx
// callback ⇒ generator
// callback hell
const request = require('request');
const url = 'https://www.google.com';

request(url, function (error, response) {
  if (!error && response.statusCode === 200) {
    console.log('got it 1');
    request(url, function (error, response) {
      if (!error && response.statusCode === 200) {
        console.log('got it 2');
        request(url, function (error, response) {
          if (!error && response.statusCode === 200) {
            console.log('got it 3');
          }
        })
      }
    })
  }
});

// improve callback hell => generator
const request = require('request');

function* requestGen() {
  function sendRequest(url) {
    request(url, function (error, response) {
      if (!error && response.statusCode === 200) {
        console.log(response.body);

        iterator.next(response.body); // always for the next time
      }
    })
  }
  const url = 'https://www.google.com';

  const r1 = yield sendRequest(url);
  console.log('r1', r1);
  const r2 = yield sendRequest(url);
  console.log('r2', r2);
  const r3 = yield sendRequest(url);
  console.log('r3', r3);
}

const iterator = requestGen();
iterator.next(); // for the first time
// note: tight coupling and low reusability
```
関数内の`callback`は非同期関数を実現する技法のひとつですが、長くなるとコールバック地獄になりがちです。

`generator`のように一番の外側には`generator`関数、`url`のラッパー関数`sendRequest(url)`で`request(url, callback)`を包んで、`generator`の`next()`を通して`response.body`を今指している`yield`へ値をアサインする。読み方としては行ったり来たりして理解しづらいかもしれませんが、
`function* ()`⇒`async function ()`、`yield`⇒`await`に置き換えれば読みやすくなると思います。


## `callback` vs. `Promise`
そしてここで一つの疑問が生まれた。`generator`関数での`callback`と、`async`関数で`Promise`とはどこか違いますか？

まず一番の違いは`generator`の`callback`は非同期操作だけど、実質上同期操作です。ちょっと言い方が変かもしれないが。。

例えば上の例では`request(url, callback(error, response))`自体は`url`のリスポンスをゲットしてから第二引数の`callback`が動作する。そして`callback`の中身では`iterator.next(response.body)`、自分の再帰呼び出ししてからゲットした`response.body`を`r1`へ戻す。それから`console.log('r1', r1);`で`r1`を出力、`const r2 = yield sendRequest(url);`で再び`request()`を行う。
このように順番に動作するというのは、同期関数とはどこが違う？（以前から`callback`を非同期操作と呼ばれる違和感を感じたのはこれです。）あえて同期関数のように書き換えても同じことができるのでは？
```jsx
// callback
function demo(str, callback) {
  console.log(str);
  callback();
}
demo('I am str', () => {
  console.log('I am callback');
});
// I am str
// I am callback
```
https://www.jsv9000.app/?code=ZnVuY3Rpb24gZGVtbyhzdHIsIGNhbGxiYWNrKSB7DQogIGNvbnNvbGUubG9nKHN0cik7DQogIGNhbGxiYWNrKCk7DQp9DQpkZW1vKCdJIGFtIHN0cicsICgpID0%2BIHsNCiAgY29uc29sZS5sb2coJ0kgYW0gY2FsbGJhY2snKTsNCn0pOw%3D%3D


`Promise`は違いますね。前にも少し触れたが`Event Loop`では**解決された**`Promise`を別のキュー`Microtask`に送り、`Call stack`がクリアしてから順番通りに結果を実行していくんです。（`new Promise(function(resolve, reject))`が解決状態になるまで同期に動いています。）
```jsx
// Promise
function PromiseDemo() {
  return new Promise((resolve, reject) => {
    console.log('number in Promise')
    resolve('I am Promise');
  })
}

PromiseDemo().then((result) => {
  console.log(result);
});
console.log('1');
PromiseDemo().then((result) => {
  console.log(result);
});
console.log('2');
// number in Promise
// 1
// number in Promise
// 2
// I am Promise
// I am Promise
```


https://www.jsv9000.app/?code=ZnVuY3Rpb24gUHJvbWlzZURlbW8oKSB7DQogIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7DQogICAgY29uc29sZS5sb2coJ251bWJlciBpbiBQcm9taXNlJykNCiAgICByZXNvbHZlKCdJIGFtIFByb21pc2UnKTsNCiAgfSkNCn0NCg0KUHJvbWlzZURlbW8oKS50aGVuKChyZXN1bHQpID0%2BIHsNCiAgY29uc29sZS5sb2cocmVzdWx0KTsNCn0pOw0KY29uc29sZS5sb2coJzEnKTsNClByb21pc2VEZW1vKCkudGhlbigocmVzdWx0KSA9PiB7DQogIGNvbnNvbGUubG9nKHJlc3VsdCk7DQp9KTsNCmNvbnNvbGUubG9nKCcyJyk7

JavaScriptは`single-threaded`なので、細かく言えば`callback`も`Promise`も一つのスレッドで優先度が違うだけで同期に動作するしか見えないかもしれないけれど、**解決された**`Promise`はほかのキューに移すというのはスレッドをブロッキングしない遮断しない非同期操作に近い動きを実現してくれました。

ブラウザ側ではほかのタスクキューまたスレッドも存在する、JavaScriptエンジンはその中の一つのスレッドしか過ぎませんが、JavaScriptエンジンを通してスレッドたちへ指示を出したり結果（タスク）を処理したりする`Multi-thread`ではないけれど`Multifunctional`です。

# `generator` ⇒ `Thunk`
https://juejin.cn/post/6844904133577670664

`Thunk`関数の仕組みを勉強してみました。簡潔に言うと（上の例のような）`generator`の結合度を下げ再利用を図るための解決策です。`Thunk`関数は三つの関数から組み立てています。`request`の例にしてみれば、

`Thunk`：第一階層引数は関数、第二階層は`URL`、第三階層はコールバック、返り値は第一の関数が`call`メソッドを通して第二と第三の引数を利用した結果を返す。

`run`：ジェネレータ関数を受け入れ、カスタムした`next`メソッドが二回目からの呼び出し`result.value(next)`では`Thunk`の`callback`関数（＝`request(url, callback)`の`callback`）として作用し、`request`の`response`が`next(error, data)`の`data`になり、`gen.next(data)`の引数`data`として、ジェネレータ関数の`yield`を通じて結果を転送する。
`done: true`になるまで何度もカスタム`next`の再帰呼び出しと、カスタム`next`内の`gen.next(data)`を経由して`request`の`response`を回送する。

`generator`：`Thunk`関数の応用をし結果を受け取ったり、それからの操作をしたりもする。

**（以下はデモコードです。最新バージョンではない。）**
```jsx
// improve generator tight coupling problem => Thunk function
function Thunk(fn) { // fn => request
  return function (...args) {
    return function (callback) {
      // fn.call => request.call(), args => url
      // callback => (first time)request's callback => (second time)custom next()
      return fn.call(this, ...args, callback);
    }
  }
}

function run(fn) {
  let gen = fn(); // run generator

  // custom next method === request's callback, data => response
  function next(error, data) {
    // in the first time, result => gen.next()
    // when we call gen.next second time forward, data => request's response
    let result = gen.next(data);

    if (result.done) return;
    
    // result.value => return function(callback) { return request.call(this, url, callback) }
    // result.value(callback) => custom next()
    result.value(next);
  }

  next();
}

const request = require('request');
const requestThunk = Thunk(request);

// generator
function* requestGen() {
  const url = 'https://www.google.com';
  let r1 = yield requestThunk(url);
  console.log(r1.body);
  let r2 = yield requestThunk(url);
  console.log(r2.body);
  let r3 = yield requestThunk(url);
  console.log(r3.body);
}

run(requestGen);
/**
 * generator.next() always send value back to
 * yield first, then execute method.
 */
```

# `co`
`co`の引数はジェネレータ、返り値は`Promise`オブジェクトです。
内部コード`yield`に`Thunk`関数をサポートするけど`callback`をいれる`Thunk`しか受け入れられないので、`fn`と`args`がすでにラッピングしておかねばならない。
```jsx
function Thunk(fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  }
}

const request = require('request');
const requestThunk = Thunk(request); // fn => request

const googleRequest = requestThunk('https://www.google.com'); // args => url
const co = require('co');
co(function* () {
  const r1 = yield googleRequest; // run => googleRequest(callback)
  const r2 = yield googleRequest;
  const r3 = yield googleRequest;

  return {
    r1,
    r2,
    r3
  }
}).then((response) => {
  console.log(response);
}).catch((error) => {
  console.log(error);
})
```
`co`では`yield`が`Thunk`（`callback`）か`Promise`を受け入れるが、未来サポートしない可能性があるので`Promise`のほうがおすすめです。
```jsx
//
const co = require('co');
co(function* () {
  const url = 'https://www.google.com';
  const r1 = yield fetch(url);
  const r2 = yield fetch(url);
  const r3 = yield fetch(url);

  return {
    r1,
    r2,
    r3
  }
}).then((response) => {
  console.log(response);
}).catch((error) => {
  console.log(error);
});
```

## about `co` module
- 引数は`generator`。
- `yield`には`Thunk`か`Promise`が受け入れられる。
- 返り値が`Promise`を保証する。

https://es6.ruanyifeng.com/?search=asyncIterator&x=2&y=15#docs/generator-async#co%20%E6%A8%A1%E5%9D%97

```jsx
// co module simplifies generator without calling next(),
// and it returns Promise object which can use callback in Promise chain
let co = require('co');
co(gen).then(function () {
  console.log('Generator is completed')
});
```
下は参考文章から取った`co`の内部を再現するデモコードです。（`Promise`に基づいたデモコードです。本番のソースコードは下の`co module source code`にあります。）

`co`は最終的に`Promise`を返すのがポイントです。`Thunk`は`callback`から貰った返り値、`Promise`は解決された`Promise`です。`yield`には`Promise`ともかく、なぜ`Thunk`も受け入れられるでしょうか。

このデモコードでは`fs`の`callback`からもらった結果`data`を`resolve()`へ、つまり`Thunk`の返り値でも解決された`Promise`にすれば最終的に`Promise`を返すのが保証される。
```jsx
// internal implementation
const fs = require('fs');

const readFile = function (fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (error, data) => {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/directory/file1');
  const f2 = yield readFile('/directory/file2');
  console.log(f1.toString());
  console.log(f2.toString());
};
// note: yield is a two-way syntax
```
`co`にはもうひとつのポイントがあります。それは`generator`の`next()`を自動的に繰り返すことです。今の`co`は`Promise`を保証するのだから`Promise chain`が利用できます。

手動なら、コールバック地獄のようにならないけれど長くなると読みづらくなります。`Thunk`のように`callback`で`done: true`になるまで自分（custom next）を呼び出しすれば自動的に繰り返してくれます。
```jsx
// manually run
let g = gen();
// value was Promise object
// first, get file1 and put value into callback
g.next().value.then(function (data) {
  // second, send file1 back to f1, then get file2 and put into callback
  g.next(data).value.then(function (data) {
    // finally, send file2 back to f2
    g.next(data);
  });
});

// automatically run
function run(gen) {
  const g = gen();

  function next(data) {
    // for the first time we call next(), there doesn't have "data", that means we runs "g.next()"
    let result = g.next(data);
    // if all the file loading were done, it returned value back finally,
    if (result.done) return result.value;
    // if not, we use Promise chain to send value back and  call next() again until all the files were done
    result.value.then(function (data) {
      next(data);
    });
  }
  next();
}
// note: we can use next() to point to where yield is,
// and send value back to yield by next() at the same time
run(gen);
```
（自分の感覚では`co`は`Thunk`関数の改善バージョン、`Thunk`は手動`generator`の改善バージョンです。）

`generator`を核心概念として、
`Thunk`は`callback`返り値のためのラッパー、
`co`は`callback`返り値が`Promise`を保証するラッパー、
`run`の`custom next`は`generator`を自動的に動くための再帰呼び出しメソッドです。

## `co` module source code

https://es6.ruanyifeng.com/?search=asyncIterator&x=2&y=15#docs/generator-async#co%20%E6%A8%A1%E5%9D%97

勉強のために一部のコードを取って概念を説明したいと思います。
**（以下はデモコードです。ネーミング変更あり。）**
```jsx
// co module source code
function co(gen) {
  let contextOfCo = this;
  return new Promise((resolve, reject) => {
    // check gen if generator(function)
    // if it is, change generator's context with co function
    if (typeof gen === 'function') gen = gen.call(contextOfCo);

    // avoiding gen is null/undefined or it didn't have next function(not generator), return and suspend
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();
    function onFulfilled(resolve) {
      let result;
      // let generator run automatically
      try {
        result = gen.next(resolve);
      } catch (e) {
        return reject(e);
      }
      next(result);
    }
  });
}

function next(result) {
  // first: check generator state
  if (result.done) return resolve(result.value);
  // second: ensure return value is Promise object
  // invoke external function: toPromise()
  let value = toPromise.call(contextOfCo, result.value);
  // third: if value is Promise object, use Promise chain to call onFulfilled function again
  // invoke external function: isPromise()
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  // forth: if value is not a function, Promise, generator, array, or object
  return onRejected(
    new TypeError(
      'You may only yield a function, promise, generator, array, or object, '
      + 'but the following object was passed: "'
      + String(result.value)
      + '"'
    )
  );
}
```
`Thunk`関数の概念が分かったら`co`はそれに基づいて`Promise`の検証や保証をいれた関数です。`co`ではデータタイプ幅広く受け入れるためには返り値`Promise`の保証を細かく施しています。

https://github.com/tj/co/blob/master/index.js

ソースコードを覗いてみれば`toPromise()`では、`thunkToPromise()`、`arrayToPromise()`、`objectToPromise()`などで転換作業して、とてもよく考えられている工夫されているモジュールだと分かります。

下は使い方のデモコードです。
```jsx
// usage
// let actions execute asynchronously
// when all of them are completed, it will continue to next step
// array
co(function* () {
  let resolve = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(resolve);
}).catch(onerror);
// object
co(function* () {
  let resolve = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2)
  };
  console.log(resolve);
}).catch(onerror);

co(function* () {
  let values = [n1, n2, n3];
  yield values.map(somethingAsync);
});

function* somethingAsync(x) {
  // do something async
  return y
}
// note: the argument in map() works simultaneously
```

ここからは`async function`に入りますが、後から読めば文章が長すぎて読みづらくなるので`async function`の部分はpart2に続きます。