async awaitでの繰り返し処理についての小話
TRANSCRIPT
async await での繰り返し処理についての小話
2017.1.27Developers in KOBE Vol. 11
BathTimeFish 村岡 正和
HTML5-WEST.jp 代表 / html5j マークアップ部 部長 / HTML5 Experts.jp メンバーNPO 法人日本ウェアラブルデバイスユーザー会理事神戸市ウェアラブルデバイス推進会議メンバーなど
むらおか まさかず
村岡正和Web アプリケーション開発 IT 業務システム設計 / 開発
Web サービス導入 / 事業戦略コンサルティング神戸デジタル・ラボ 社外取締役
@bathtimefish
HTML5-WEST.jp
英語ができないのに外国で英語プレゼンしてきた件
async / await やってます?
https://tc39.github.io/ecmascript-asyncawait/
babel で stage 3 つかってますが
async ファンクション内で繰り返し処理をやったとき「あーなるほどなー」って
思ったのでちょっと話します。
Array.forEach をやめてfor … of とか for … in とか使おうぜ
って話です。
具体的には、
とりあえず、検証環境のセットアップ
mkdir async-test && cd $_npm initnpm i -D babel-clinpm i -D babel-polyfillnpm i -D babel-preset-es2015npm i -D babel-preset-stage-3
babel のセットアップ
.babelrc を作成する
{ "presets": [ "es2015", "stage-3" ]}
package.jsop のようす
"devDependencies": { "babel-cli": "^6.22.2”, "babel-polyfill": "^6.22.0" "babel-preset-es2015": "^6.22.0", "babel-preset-stage-3": "^6.22.0" }
検証コード 1forEach で await を繰り返す
import 'babel-polyfill';
function sleep(count) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(count); }, count*1000); });}
async function Main() { let times = [1, 2, 3]; times.forEach((t) => { let time = await sleep(t); console.log(time.toString() + ' times.'); });}
Main();
await は予約語だと怒られる。
$ babel foreach-not-work.js -o test.jsSyntaxError: foreach-not-work.js: await is a reserved word (14:15) 12 | let times = [1, 2, 3]; 13 | times.forEach((t) => {> 14 | let time = await sleep(t); | ^ 15 | console.log(time.toString() + ' times.'); 16 | }) 17 | }
なぜか?
Array.forEach() 内のクロージャにasync キーワードが影響してない
代替案をさがす
for … of 文
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of
検証コード 2for…of で await を繰り返す
import 'babel-polyfill';
function sleep(count) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(count); }, count*1000); });}
async function Main() { let times = [1, 2, 3]; for (let t of times) { let time = await sleep(t); console.log(time.toString() + ' times.'); }}
Main();
コンパイルが通った。
$ babel forof-works-good.js -o test.js$
実行すると怒られた。$ node test.js/Users/btf/src/node.js/async-await-for/test.js:4 var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { ^
ReferenceError: regeneratorRuntime is not defined at /Users/btf/src/node.js/async-await-for/test.js:4:32 at Object.<anonymous> (/Users/btf/src/node.js/async-await-for/test.js:83:2) at Module._compile (module.js:571:32) at Object.Module._extensions..js (module.js:580:10) at Module.load (module.js:488:32) at tryModuleLoad (module.js:447:12) at Function.Module._load (module.js:439:3) at Module.runMain (module.js:605:10) at run (bootstrap_node.js:420:7) at startup (bootstrap_node.js:139:9)
ぐぐってみると babel-polyfill が効いてない感じ。
test.js を開いてみる
'use strict';
var Main = function () { var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { var times, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, t, time;
return regeneratorRuntime.wrap(function _callee$(_context) {
…( 中略 )…
require('babel-polyfill');
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if
Main() の後に babel-polyfill を呼んでる。なんで? babel のバグ?
とりあえず手で直してみた。(本題とは関係ないので)
'use strict';
require('babel-polyfill');
var Main = function () { var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { var times, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, t, time;
return regeneratorRuntime.wrap(function _callee$(_context) {
…( 省略 )…
実行すると動いた。
$ node test.js1 times.2 times.3 times.
他にも似たような話が issue にあがってましたhttps://github.com/babel/babel/issues/909
検証コード 3async キーワードをつける
import 'babel-polyfill';
function sleep(count) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(count); }, count*1000); });}
async function Main() { let times = [1, 2, 3]; times.forEach( async (t) => { let time = await sleep(t); console.log(time.toString() + ' times.'); }); console.log(‘done’);}
Main();
コンパイルが通った。
$ babel foreach-bad-work.js -o test.js$
実行すると動いた…けどおかしい
$ node test.jsdone1 times.2 times.3 times.
Array.forEach が同期してない挙動
ここでも for…of 文推奨のコメントありhttps://github.com/babel/babel/issues/909#issuecomment-76468515
小話まとめ
async function 内で await 回すときにはArray.forEach 使わないようにしようArray.forEach よく考えると例外的に同期処理だったり break できない問題とかあって微妙に使いどころ考えたりするやつ
配列回すのはもう for…of 一択でよくね?
おまけ
JS に async/await 実装のアイデアはけっこう古い (2012 年頃 )
http://maxtaco.github.io/coffee-script/
async/await に至るまでの実装の経緯を昔話しました。http://www.slideshare.net/bathtimefish/async-flow-controll-basic-and-practice
Thanks !