ES2017 async/await + Promise で解決できる事、とES2015(ES6) generators (yield) + Promise + npm aa (async-await) で解決できる事

# ES2017 `async/await` + `Promise` で解決できる事

ES2017 `async/await` と `Promise` を使うと非同期処理をすごく簡単に処理できる。

とても便利なのだが、それだけでは、どうも機能が足りない様に見える。

> この記事は... **TL;DR**
ES2017 async/await を使っても、まだいろいろと課題は残ってるよ。
ES2015 (ES6) generators と [npm aa](https://www.npmjs.com/package/aa) ([async-await](https://www.npmjs.com/package/async-await)) だと、より良い解決策があるよ。って話。

以下の図の様な非同期処理フローを考えてみる。

![es7.png](https://qiita-image-store.s3.amazonaws.com/0/16128/05b2d4f6-3545-7af1-077a-fcf4c94ad6ac.png)

横軸は時間だ。左から右へ時間が流れていくものと考えて欲しい。
縦線は複数の処理を同期させたい、という意味だ。

ES2017 `async/await` 版のコード例を見てみよう。

```js:es2017-async-await-ex.js
	'use strict';

	//require('babel-polyfill'); // おまじない
	//require('regenerator').runtime();

	console.log('main: start');
	main().then(function (val) {
		console.log('main: finish:', val);
	});

	async function main() {
		var result;
		console.log('main: started');

		// シーケンシャル処理(逐次処理)
		result = await sleep(200, 'a1');
		console.log('main-a1: sleep: a1 =', result);
		result = await sleep(100, 'a2');
		console.log('main-a2: sleep: a2 =', result);
		result = await sleep(300, 'a3');
		console.log('main-a3: sleep: a3 =', result);

		// パラレル処理(並行処理)
		result = await Promise.all([sleep(200, 'b1'), sleep(100, 'b2'), sleep(300, 'b3')]);
		console.log('main-b : Promise.all([promises]): [b1, b2, b3] =', stringify(result));

		// asyncなsub()をawaitで呼ぶ
		result = await sub('c');
		console.log('main-c : sub(c) =', result);
		// asyncなsub()を並行処理で呼ぶ
		result = await Promise.all([sub('d1'), sub('d2')]);
		console.log('main-d : Promise.all([promises]): [d1, d2] =', stringify(result));

		return 'return value!';
	}

	async function sub(val) {
		await sleep(100, val + '-x1');
		console.log('sub-' + val + '-x1: sleep: ' + val + '-x1');

		await sleep(100, val + '-x2');
		console.log('sub-' + val + '-x2: sleep: ' + val + '-x2');

		return val;
	}

	function sleep(msec, val) {
		return new Promise(function (resolve, reject) {
			setTimeout(resolve, msec, val);
		});
	}

	function stringify(object) {
		var str = '';
		if (object instanceof Array) {
			for (var i = 0, n = object.length; i < n; ++i)
				str += (str ? ', ' : '') + object[i]
			return '[' + str + ']';
		}
		else {
			for (var key in object)
				str += (str ? ', ' : '') + key + ':' + object[key]
			return '{' + str + '}';
		}
	}
```


最初の `require('babel-polyfill')` は、本来のES2017では不要だと思うけどBabelを使う上での、おまじないだ。

`await sleep(200, 'a1');` の部分は非同期処理を開始して、制御を他に譲って、
非同期処理が終わった時に戻ってくる構文だ。

+ シーケンシャルな処理であれば、`await` を使うだけで良い。
+ 更に深く処理を実行したければ `await sub();` で良い。

やった。これでいいじゃん。と思うけど...

でも、パラレルな処理をやりたければ、`Promise.all([promises,...]);` などを使う必要がある。

+ 処理を2つにわけたかったら? 合流したかったら? → `Promise.all` を使う
+ 合流せずに途中結果だけを同期したかったら? → ???
+ 繰り返しイベントが発生する様なものは? ストリームは? インターバルタイマーは? → ???

ちょっと欲しい機能が足りないんじゃないかな。
更に、ES2017は今はまだブラウザでは標準とは言い難い。

ブラウザではまだ動かない。Babel が必要だね。それでいいのかな。

> 記事書いている時にbabel 6.0が出た。plugin を使うと良い。

# ES2015 (ES6) generators (yield) + Promise + [npm aa](https://www.npmjs.com/package/aa) ([async-await](https://www.npmjs.com/package/async-await)) で解決できる事

ES2015 (ES6) generators (yield) と Promise + [npm aa](https://www.npmjs.com/package/aa) ([async-await](https://www.npmjs.com/package/async-await)) を使っても非同期処理をすごく簡単に処理できる。

以下の図の様な非同期処理フローを考えてみる。

![es2015.png](https://qiita-image-store.s3.amazonaws.com/0/16128/3de689e4-3119-4a3b-648e-118ca6065929.png)

ES2015 (ES6) generators (yield) 版のコード例を見てみよう。

```js:es2015-async-await-ex.js
	// require('regenerator').runtime()           // babel & browserifyするなら入れておく
	// var aa = require('aa');
	var aa = (this && this.aa) || require('aa');  // 普通は var aa = require('aa'); で良い
	var Promise = aa.Promise;                     // native Promiseがあれば不要

	console.log('main: start');
	aa(main()).then(function (val) {
		console.log('main: finish:', val);
	});

	function *main() {
		var result;
		console.log('main: started');

		// シーケンシャル処理(逐次処理)
		result = yield sleep(200, 'a1');
		console.log('main-a1: sleep: a1 =', result);
		result = yield sleep(100, 'a2');
		console.log('main-a2: sleep: a2 =', result);
		result = yield sleep(300, 'a3');
		console.log('main-a3: sleep: a3 =', result);

		// パラレル処理(並行処理) ... 配列やオブジェクトで結果を取得
		result = yield [sleep(200, 'b1'), sleep(100, 'b2'), sleep(300, 'b3')];
		console.log('main-b : parallel Array :', stringify(result));
		result = yield {x:sleep(200, 'c1'), y:sleep(100, 'c2'), z:sleep(300, 'c3')};
		console.log('main-c : parallel Object:', stringify(result));
		result = yield Promise.all([sleep(200, 'd1'), sleep(100, 'd2'), sleep(300, 'd3')]);
		console.log('main-d : Promise.all([promises,...]): [d1, d2, d3] =', stringify(result));

		// generatorのsub()をyieldで呼ぶ
		result = yield sub('e');
		console.log('main-e : sub(e) =', result);

		// パラレル処理(並行処理) ... 配列やオブジェクトで結果を取得
		// generatorのsub()を並行処理で呼ぶ ... 配列
		result = yield [sub('f1'), sub('f2')];
		console.log('main-f : [generator,...]: [f1, f2] =', stringify(result));
		// generatorのsub()を並行処理で呼ぶ ... オブジェクト
		result = yield {x:sub('g1'), y:sub('g2')};
		console.log('main-g : {x:generator, y:...}: {x:g1, y:g2} =', stringify(result));

		// 必要ないけど無理やりgeneratorのsub()をpromiseにしてみた
		result = yield Promise.all([aa(sub('h1')), aa(sub('h2'))]);
		console.log('main-h : Promise.all([aa(generator),...]): [h1, h2] =', stringify(result));

		return 'return value!';
	}

	function *sub(val) {
		yield sleep(100, val + '-x1');
		console.log('sub-' + val + '-x1: sleep: ' + val + '-x1');

		yield sleep(100, val + '-x2');
		console.log('sub-' + val + '-x2: sleep: ' + val + '-x2');

		return val;
	}

	function sleep(msec, val) {
		return new Promise(function (resolve, reject) {
			setTimeout(resolve, msec, val);
		});
	}

	function stringify(object) {
		var str = '';
		if (object instanceof Array) {
			for (var i = 0, n = object.length; i < n; ++i)
				str += (str ? ', ' : '') + object[i]
			return '[' + str + ']';
		}
		else {
			for (var key in object)
				str += (str ? ', ' : '') + key + ':' + object[key]
			return '{' + str + '}';
		}
	}
```

ES2017版の `async function ()` を `function *()` にして、`await` を `yield` にすれば、ほぼ同じコードだ。

Node.js版は...
[npm aa](https://www.npmjs.com/package/aa) ([async-await](https://www.npmjs.com/package/async-await)) を使うだけで、
ES2015 (ES6) generators (yield) はもう標準なので、そのまま動く。
※Babel を使わなくても良い

```bash
$ npm install --save aa
$ node es2015-async-await-ex
```

HTML版の Chrome, Firefox, Edge では...
[npm aa](https://www.npmjs.com/package/aa) ([async-await](https://www.npmjs.com/package/async-await)) と依存している [npm promise-thunk](https://www.npmjs.com/package/promise-thunk) を使うだけで、
ES2015 (ES6) generators (yield) はもう標準なので、そのまま動く。
※Babel, Browserify を使わなくても良い

```html:es2015-async-await-ex.html
<!doctype html>
<meta charset="UTF-8">
<script src="https://lightspeedworks.github.io/console-to-window/console-to-window.js"></script>
<script src="https://lightspeedworks.github.io/promise-thunk/promise-thunk.js"></script>
<script src="https://lightspeedworks.github.io/aa/aa.js"></script>
<script src="es2015-async-await-ex.js"></script>
```

HTML版の Internet Explorer では...
Babel, Browserify を使ってください。

# 合流せずに途中結果だけを同期したかったら?

以下の図の様な非同期処理フローを考えてみる。

![es2015-fork-join.png](https://qiita-image-store.s3.amazonaws.com/0/16128/32b665a9-b65e-4ebe-fecc-e7a866de6445.png)

[npm aa](https://www.npmjs.com/package/aa) の `aa.Channel` を使うと自由に同期できる。`yield chan;`とすると同期できる。
処理を分割(fork)して合流(join)したい時、generatorの配列を使うと良い。
 
```js:es2015-fork-join.js
	var aa = require('aa');
	var Channel = aa.Channel;

	console.log('main: start');
	aa(main()).then(function (val) {
		console.log('main: finish:', val);
	});

	function *main() {
		var result;
		console.log('main: started');

		var chan = Channel();

		yield [
			function *() {
				console.log('main:', yield sleep(100, 'a1'));
				console.log('main:', yield sleep(200, 'a2'));
				chan('a1-a2');  //----------------------------------+
				console.log('main:', yield sleep(300, 'a3')); //    |
			},                                                //    |
			function *() {                                    //    |
				console.log('main:', yield sleep(200, 'b1')); //    |
				console.log('main:', yield chan, '-> b'); // <------+
				console.log('main:', yield sleep(200, 'b2'));
			}
		];

		return 'return value!';
	}

	function sleep(msec, val) {
		return new Promise(function (resolve, reject) {
			setTimeout(resolve, msec, val);
		});
	}
```

# 繰り返しイベントが発生する様なものは? ストリームは? インターバルタイマーは?

[npm aa](https://www.npmjs.com/package/aa) の `aa.Channel` を使うと自由に繰り返し同期できる。

ストリームは...

```js:es2015-stream-ex.js
	var fs = require('fs');
	var aa = require('aa'), Channel = aa.Channel;

	aa(function *main() {
		var chan = Channel().stream(fs.createReadStream('es2015-stream-ex.js', {encoding: 'utf8'}));
		var writer = fs.createWriteStream('es2015-stream-ex.log', {encoding: 'utf8'})

		var buff;
		while (buff = yield chan)
			writer.write(buff);
		writer.end();
	});
```

インターバルタイマーは...

```js:es2015-interval-ex.js
	var aa = require('aa'), Channel = aa.Channel;

	aa(function *main() {
		var chan = Channel();

		var countDown = 10;
		var interval = setInterval(function () { chan(countDown--); }, 100);

		var val;
		while (val = yield chan)
			console.log('count:', val);

		clearInterval(interval);
	});
```


# あまりにも並列度が多過ぎて過負荷になる事を避けたい時は? 並列度に制限を持たせたい。

[npm executors](https://www.npmjs.com/package/executors) を使うと良い。
並列処理だとすぐに終わるが、大量にリソースを食うものだと、問題になる事がある。
例えばDBアクセスとか、プロセス起動などだ。
並列度がはっきりとわかっている場合は、まだ良いが、多数のクライアントからの要求は、
並列度の制限を設けたい場合もあるのだ。

9並列になってしまう場合に3並列に抑えたいという時は、並列度に制限をかけたい所に
`var executors3 = Executors(3);` などとして、`yield executors3(fn, args,...);` とすると良い。
同時に3並列以上実行しない様にできる。

```js:es2015-executors-ex.js
	var aa = require('aa'), Channel = aa.Channel;
	var Executors = require('executors');

	aa(function *main() {
		var parallel9 = [];
		console.time('parallel9');
		for (var i = 0; i < 9; ++i)
			parallel9.push(sleep(100, i));
		yield parallel9;
		console.timeEnd('parallel9');

		var executors3 = Executors(3);
		var parallel3 = [];
		console.time('parallel3');
		for (var i = 0; i < 9; ++i)
			parallel3.push(executors3(sleep, 100, i));
		yield parallel3;
		console.timeEnd('parallel3');
	});

	function sleep(msec, val) {
		return new Promise(function (resolve, reject) {
			setTimeout(resolve, msec, val);
		});
	}
```

# その他 promisify, thunkify など

aa.promisify(), aa.thunkify(), aa.promisifyAll(), aa.thunkifyAll() などを駆使すれば、結構いろんな事ができる。

```js
var fs = require('fs');

// 単独のメソッドを aa で yield できる様にする。
aa.promisify(fs, 'exists', {suffix: 'A'});
// または
aa.thunkify(fs, 'exists', {suffix: 'A'});

// オブジェクトのメソッド群を aa で yield できる様にする。
aa.promisifyAll(fs, {suffix: 'A'});
// または
aa.thunkifyAll(fs, {suffix: 'A'});

if (yield fs.existsA('file.txt')) {
    var buff = yield fs.readFileA('file.txt', {encoding: 'utf8'});
}

// 例えば postgres の場合は、以下の様な感じだ。
var pg = require('pg');
aa.promisifyAll(pg.constructor.prototype);
aa.promisifyAll(pg.Client.prototype);
// etc...
```

# 参考文献

以下のリンク先も参考にして欲しい。

[[JavaScript] 非同期処理のコールバック地獄から抜け出す方法 - Qiita]
(https://qiita.com/LightSpeedC/items/7980a6e790d6cb2d6dad)