LoginSignup
15
15

More than 5 years have passed since last update.

フロントエンドでも、非同期でループとか合流とかの制御構造を書く(Deferred, Promiseよりも直感的に)

Last updated at Posted at 2015-06-09

node-asyncとは違うよ

node.jsにはasyncというライブラリがあるが、これはnextTickなどのnode独自の機構を用いて制御構造を実現するため、制御構造のあちこちに「隙間」が開くし、クライアントサイドにそのまま移植できない。本稿は優れて一般的な、そして内部的にシンプルな解を提供する。

非同期処理のループ、合流、順次実行なら「control-structures.js」

非同期関数を使って、ループとか、if,switchからの合流とかを実現したいときの選択肢を探してGoogle先生に聞いてみると、、、Deferred一択みたいな雰囲気です。でも、そっかなー、「日常使い」には、Deferredってなんか仰々しい気がするなー、と私は思うのです。だってさ、もともと高階関数が使えるJavascriptって、非同期処理に向いているんですよ? その言語機能を生かしたいじゃないですか。

Deferredっていうのは、なんつーか、組み合わせ自由なパーツを提供するライブラリに向いている仕組みです。色んなパーツを提供して、「ほれ、組み合わせて使ってみ?」っていう時のための「継ぎ手」として、ライブラリの全メソッドにPromise仕込んどく、ってのは有効です。そしてパワフル。だから、jQueryのメソッドをDeferredでつなぎ合わせる、という使い方は手軽で快適で、そして設計解として優秀だと思います。

でもね、forとかwhileとかif,switch後の合流とかに一般的に使うとなると、全部ラップするのはなんか仰々しいです。それにさ、普通の制御構造を見慣れている目で見ると、.then(success,fail)って、ちょっと直感的じゃないんですよね。だから、それを組み合わせたコードはいまいち見た目が普通と違う。ラップする手間とか、状態を修正する手間とかを考えると、どうもタイプ数も多いみたいだ。となります。

そこで、拙作「control-structures.js」です。お試しの際は、下記サンプルのように、「github.io」にminified版をホストしていますので、直リンクしちゃって下さい。「Javascriptの言語機能だけを用い(外部ライブラリに依存せず)、タイプ数を極力少なくし、可能な限り自由な引数渡しを許し、同期処理と同じようなコードの見た目になる」ことを目指しました。サーバサイドでも使える、とゆーか、もともとサーバサイド用に書いたので、node.jsでも同じ書き方ができます。

以下のサンプルソースは、CoffeeScriptとJavaScriptを併記していますが、最適化が大事ならJSXでも最近はアロー記法が使える様なので、そっちでも良いと思います。いずれにしても、本番ではコンパイルして外出ししますよね。多分、遷移した後で共通ソースでキャッシュが効くように、とかも意識して。だから、コーディングには、タイプ量の少ないAltJSを使うことをお勧めします。Javascriptで書いていても、AltJSで書いていても、いずれどっかで嵌まります。嵌まったときに、AltJSでは、Javascriptで嵌まりやすいところに関しては嵌まりにくくできていますし、AltJS特有のハマリに関しては、コンパイル後のJavascriptと見比べれば大体解決します。だから、ハマリは浅いと思いますよ?

で、特に、ちょっとしたテストには、直書きCoffeeScriptはお手軽です。おすすめ。

本題の前に繰り言

ただし、「無名関数はスコープを継承するんだから、引数の受け渡しメカニズムなんていらね」というのであれば、逐次実行の「((next) -> ASYNCFUNC -> next())(-> NEXTASYNCFUNC())」みたいなイディオムと、Yコンビネータと「_while = (loopfunc, endfunc) -> (y (func) -> return -> loopfunc(endfunc,func))()」みたいなワンライナーwhileだけで自由自在です。Deferredはおろか、この「control-structures.js」さえも不要です。元々、node-er向けに制御構造完全制覇を書いたのは、そーゆー技があればコルーチンとか要らないよね。部品の細かいライブラリを提供するとき以外、Promiseも要らないよね。という気持ちからだったのです。しかしまあ、歴史上、鍛錬でどうこうするアプローチが栄えた試しはないので、最小限のライブラリを作ってみた、というわけです。例えばさ、C/C++で、正しくconstが付けられたら凄く分かりやすくて良い構造化ができるのに、結局「const制約」ってその後系譜が絶えてしまったし。

では、そろそろ本題です。

if,switchなどの条件分岐からの合流

まずは、if,switchからの合流です。Deferredで案外めんどくさく、直感的でないのがこれです。だってさ、状態変化させるコードを書けば合流するって、そりゃないよね。

うちの場合、ブロックを順次実行する「cs._」メソッドの中にif文やswitch文で書き、次のブロックに進みたい時にnext()をコールバックに入れると、次のブロックに進みます。nextに引数を付けると、ごっそり次のブロックの引数に引き継がれます。

実は、ライブラリなんか噛まさなくとも

((next) ->
  A ->
    if daijobu?
      B ->
        next()
    else
      BB ->
        next()
)(->
  C ->
    D -> return

つー書き方があります。これで良いなら、これにしましょう。ライブラリ依存なんて少ない方が良いです。ただ、この書法は、3つ以上の順次実行をしようとすると次第に汚くなるので(慣れたら慣れたで職人技コードと呼ばれかねない世界に足を踏み込むので)ライブラリを使うのが良いですね。下記、CoffeeScript版とJavascript版のサンプルです。

test-coffee-if,switch.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._ ['arg1','arg2']
  ,(next,str1,str2) ->

    PRINT '[1st block]',->
      if str1 == str2
        PRINT 'str1 and str2:'+str1,->
          next()
      else
        PRINT 'str1:'+str1,->
          PRINT 'str2:'+str2,->
            next()

  ,(next) ->

    PRINT '[2nd block]',->
      next('arg','arg')

  ,(next,str1,str2) ->

    PRINT '[3rd block]',->
      if str1 == str2
        PRINT 'str1 and str2:'+str1,->
          next()
      else
        PRINT 'str1:'+str1,->
          PRINT 'str2:'+str2,->
            next()

  ,(next) ->

    PRINT '[last block]',->
      next()

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-if,switch.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._(['arg1', 'arg2'], function(next, str1, str2) {
      PRINT('[1st block]', function() {
        if (str1 === str2) {
          PRINT('str1 and str2:' + str1, function() {
            next();
          });
        } else {
          PRINT('str1:' + str1, function() {
            PRINT('str2:' + str2, function() {
              next();
            });
          });
        }
      });
    }, function(next) {
      PRINT('[2nd block]', function() {
        next('arg', 'arg');
      });
    }, function(next, str1, str2) {
      PRINT('[3rd block]', function() {
        if (str1 === str2) {
          PRINT('str1 and str2:' + str1, function() {
            next();
          });
        } else {
          PRINT('str1:' + str1, function() {
            PRINT('str2:' + str2, function() {
              next();
            });
          });
        }
      });
    }, function(next) {
      PRINT('[last block]', function() {
        next();
      });
    });

});
/*
   ここまで test code
 */
</script>
</body>
</html>

単純な順次実行

さて、ただの順次実行。Deferredのthenでもこれはかわりありません。敢えて言えば、Promiseでラップする必要がないこと。まあ、もしif,switchからの合流のためにこのライブラリを使うなら、順次実行でネストが深すぎることになる、いわゆる「コールバック地獄」の対策にも同じライブラリを使おうよ、って話。順番が逆なのは、if,switchの方がメリットを感じやすいからです。

もう説明は良いですね。「cs._」の後に、初回の引数、以下好きな数だけブロックを並べます。CoffeeScriptみたいに、レイアウトベースのAltJSを使ってもらうと、各ブロックがかなりコピペラブルになっていることがおわかりでしょう。なんか、ゆるくて良い感じでしょ?

test-coffee-simple.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._ ['arg1','arg2']
  ,(next,str1,str2) ->

    PRINT '[1st block]',->
      PRINT 'str1:'+str1,->
        PRINT 'str2:'+str2,->
          next 'err'

  ,(next,errstr) ->

    PRINT '[2nd block]',->
      PRINT 'errstr:'+errstr,->
        next()

  ,(next) ->

    PRINT '[last block]',->
      next()

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-simple.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._(['arg1', 'arg2'], function(next, str1, str2) {
      PRINT('[1st block]', function() {
        PRINT('str1:' + str1, function() {
          PRINT('str2:' + str2, function() {
            next('err');
          });
        });
      });
    }, function(next, errstr) {
      PRINT('[2nd block]', function() {
        PRINT('errstr:' + errstr, function() {
          next();
        });
      });
    }, function(next) {
      PRINT('[last block]', function() {
        next();
      });
    });

});
/*
   ここまで test code
 */
</script>
</body>
</html>

並行実行はこれくらいでいい?

さて、並行実行。これもDeferredで充分だろ、という感じですね。どうせ並行実行して待ち合わせする価値のあるコードなんて、配列やオブジェクトの走査だけだろ、という投げやりな思いが溢れています。とは言え、配列の各要素に並行して何かをやらせる、というのはままあることだと思いますので、使いやすいかと思います。並行実行なので、breakに相当する機能はありません。同期関数はforEachで、非同期関数は「cs._each」で、と使い分けてもらえばOKです。

「cs._」に同時実行オプション、というのはそれはそれでいやあんな感じなので、迷いました。「cs._parallel」っていうのも考えたんですが、結局配列走査だけ残しました。

任意の作業を並行実行するなら、このままでも一応できます。インラインで配列を書いて、そこに引数の配列と無名関数を入れて、後はloopfuncでそいつを起動してやる、というやり方。まあ、この程度でも変態呼ばわりされる世の中にお住まいの方は合掌。

test-coffee-each.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._each [2,3,5,7,11]
  ,['arg1']
  ,(val,i,next,str1) ->

    PRINT 'apply',->
      next(val,i)

  ,(array_of_args_from_next1,array_of_args_from_next2) ->

    for num in array_of_args_from_next1
      console.log 'value: '+num.toString()
    for num in array_of_args_from_next2
      console.log 'counter: '+num.toString()
    next()

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-each.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._each([2, 3, 5, 7, 11], ['arg1'], function(val, i, next, str1) {
      PRINT('apply', function() {
        next(val, i);
      });
    }, function(array_of_args_from_next1, array_of_args_from_next2) {
      var j, k, len, len1, num;
      for (j = 0, len = array_of_args_from_next1.length; j < len; j++) {
        num = array_of_args_from_next1[j];
        console.log('value: ' + num.toString());
      }
      for (k = 0, len1 = array_of_args_from_next2.length; k < len1; k++) {
        num = array_of_args_from_next2[k];
        console.log('counter: ' + num.toString());
      }
      return;
    });

});
/*
   ここまで test code
 */
</script>
</body>
</html>

伝統的なforによる繰り返し

伝統的なfor、つまり「forやめろ」で話題になったアレです。カウンタはライブラリが内部的に用意する形ですが、どうせ型は指定していないので好きなオブジェクトで実行できます、forの後に、初期化、条件関数、イテレータ関数、ループ初回のオプション引数(なしなら[])、ループ処理、終了後の行き先、という引数を取ります。

引数多っ!と思う向きもありましょうが、サンプルを見て頂ければ、「ブロックを引数に取る」という都合上であって、邪魔な引数を設定しているわけではありません。

ループからのbreakはループ処理に引数として渡される「_break」を呼べばOK。continueは、「_next」を呼んじゃえばOKです。それぞれに追加引数を渡せば、ちゃんと終了後の行き先関数や次巡のループに引数を受け渡します。

test-coffee-for.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._for 0,((n)-> n<10),((n)-> n+1)
  ,[]
  ,(n,_break,_next) ->

    PRINT 'counter: '+n.toString(),->
      _next()

  ,(n) ->

    PRINT 'last counter: '+n.toString(),->
      return

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-for.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._for(0, (function(n) {return n < 10;}), (function(n) {return n + 1;})
    , []
    , function(n, _break, _next) {
      PRINT('counter: ' + n.toString(), function() {
        _next();
      });
    }, function(n) {
      PRINT('last counter: ' + n.toString(), function() {
        return;
      });
    });

});
/*
   ここまで test code
 */
</script>
</body>
</html>

配列やオブジェクトの巡回

さて、forのもう一つ、配列やオブジェクトの巡回に使う「_for_in」です。引数構成もよく似ていますね。ループ関数では、key,valとか、val,indexとかを使うと思いますので、それらが渡されます。また、forと同じく「_break」「_next」も渡せます。それぞれの追加引数もちゃんと終了後の行き先関数や次巡のループに渡します。

test-coffee-forin.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._for_in {bro:'ani',sis:'imo',dad:'tousan',mom:'kaasan'}
  ,['first']
  ,(key,val,_break,_next,arg) ->

    PRINT key+': '+val,->
      PRINT arg,->
        _next('notfirst')

  ,->

    PRINT 'end',->
      return

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-forin.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._for_in({
      bro: 'ani',
      sis: 'imo',
      dad: 'tousan',
      mom: 'kaasan'
    }
    , ['first']
    , function(key, val, _break, _next, arg) {
      PRINT(key + ': ' + val, function() {
        PRINT(arg, function() {
          _next('notfirst');
        });
      });
    }, function() {});

});
/*
   ここまで test code
 */
</script>
</body>
</html>

シンプルなwhile

さて、forについては「いいたいことは分かるんだけど、やっぱりシンプルさに欠けるよな。どうせ無名関数で使うんならスコープを継承するんだし、ループ条件の判断は自分で書くよ」という鋭い方もいらっしゃるでしょう。それもまた良いでしょう。

whileはforなどと同じ仕組みですが、より単純です。引数の受け渡しメカニズムと継続ループのメカニズムはそのままに、「_break」「_next」を好きに呼び分けて下さい、というのがこの「cs._while」です。

引数を覚えるのが面倒なら、whileだけ使うってのも手ではあります。なんか「forやめろ」みたいでちょっとやな気分ではありますが。

ちなみに、引数の受け渡しを考慮しなければ、既出の通り、ワンライナーで書けます。こーゆー零細なライブラリは入れらんない、という人は、ワンライナーを貼っといて何とかすると良いかも。Yコンビネータも必要だからご注意。 _while = (loopfunc, endfunc) -> (y (func) -> return -> loopfunc(endfunc,func))() これだけ。これだけなのよ。Yコンビネータもワンライナーにしとくと、y = (f) -> ((p) -> -> f(p(p)).apply(this,arguments))((p) -> -> f(p(p)).apply(this,arguments)) 矢印2連続とかキモいけど勘弁して。ちなみに、CoffeeScriptは、最後に扱ったオブジェクトを自動的に返却するようになってるので、こういう超圧縮ができるのです。

test-coffee-while.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  cs._while [25*25, 25*88]
  ,(_break,_next,arg1,arg2) ->

    PRINT arg1,->
      PRINT arg2,->
        if (arg2 % arg1) == 0
          _break(arg1)
        else
          _next(arg2 % arg1,arg1)

  ,(arg) ->

    PRINT 'result: '+arg.toString(),->
      return

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-while.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    cs._while([25 * 25, 25 * 88], function(_break, _next, arg1, arg2) {
      PRINT(arg1, function() {
        PRINT(arg2, function() {
          if ((arg2 % arg1) === 0) {
            _break(arg1);
          } else {
            _next(arg2 % arg1, arg1);
          }
        });
      });
    }, function(arg) {
      PRINT('result: ' + arg.toString(), function() {});
    });

});
/*
   ここまで test code
 */
</script>
</body>
</html>

例外機構

さて、例外機構です。引数にブロックをいくつも取ります。処理のメインブロック、catch節、finally節、そしてその後の行き先。

Deferredが例外処理の代わりになる、という主張の危ういところは、そもそも単なるエラー処理後合流するだけのフローなら、例外処理でなくていい、という点にあります。順次実行の例を応用して、もうワンライナーで書くけど、((_catch,_fin)->foo ->if err then _catch(_e,_fin) else _fin())(((_e,_fin)->bar ->_fin()),-> baz ->return)、でもネストなし例外機構は記述できます。つまり、「ネストしない例外処理なんて元々怖くねー」っていうやつですね。リソースリークを避けるために、異常処理をした時でもfinallyでちゃんとリソース解放したいし、エラーごとにcatchすべき粒度は変わってくる、っていうような、真面目に例外処理機構を使ったことのある人のニーズに応えないといけないわけです。

とは言え、非同期処理の後に継続を書かずにreturnする、という振る舞いに対して、この例外機構は無力です。そういう意味で、finallyの強制力は強くありません。コーディングスタイルと、ある種のプリプロセッサでチェックするしかないでしょうが……。

test-coffee-exc.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/coffeescript">
###
   ここから test code
###
$(->

  PRINT = (str,next) -> console.log str; setTimeout(next, 200)

  myexc = new cs.exc
  myexc._try([]
  ,->
    #block
    PRINT 'NEST1-1',-> 
      PRINT 'NEST1-2',->
        myexc._try([]
        ,->
          #block
          PRINT 'NEST2-1',->
            PRINT 'NEST2-2',->
              if true
                myexc._throw 'err1'
              else
                PRINT 'NEST2-3',->
                  myexc._finally()
        ,['err2','err3']
        ,(_e)->
          #catch
          console.log _e
          PRINT 'NEST2-CATCH',->
            myexc._finally()
        ,(fnext)->
          #finally
          PRINT 'NEST2-FINALLY',->
            fnext()
        ,->
          PRINT 'NEST1-3',->
            myexc._finally()
        )
  ,['err1']
  ,(_e)->
    #catch
    console.log _e
    PRINT 'NEST1-CATCH',->
      myexc._finally()
  ,(fnext)->
    #finally
    PRINT 'NEST1-FINALLY',->
      fnext()
  ,->return)

)
###
   ここまで test code
###
</script>
<script type="text/javascript" src="http://mrpepper023.github.io/lib/coffee-script.js"></script>
</body>
</html>
test-javascript-exc.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Async test</title>
</head>
<body>
<h1>test</h1>
<div id="test"></div>

<!--scripts-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://mrpepper023.github.io/lib/control-structures.min.js"></script>
<script type="text/javascript">
/*
   ここから test code
 */
$(function () {

    PRINT = function(str, next) {
      console.log(str);
      return setTimeout(next, 200);
    };

    var myexc;
    myexc = new cs.exc;
    myexc._try([], function() {
      PRINT('NEST1-1', function() {
        PRINT('NEST1-2', function() {
          myexc._try([], function() {
            PRINT('NEST2-1', function() {
              PRINT('NEST2-2', function() {
                if (true) {
                  myexc._throw('err1');
                } else {
                  PRINT('NEST2-3', function() {
                    myexc._finally();
                  });
                }
              });
            });
          }, ['err2', 'err3']
          , function(_e) {
            console.log(_e);
            PRINT('NEST2-CATCH', function() {
              myexc._finally();
            });
          }, function(fnext) {
            PRINT('NEST2-FINALLY', function() {
              fnext();
            });
          }, function() {
            PRINT('NEST1-3', function() {
              myexc._finally();
            });
          });
        });
      });
    }, ['err1']
    , function(_e) {
      console.log(_e);
      PRINT('NEST1-CATCH', function() {
        myexc._finally();
      });
    }, function(fnext) {
      PRINT('NEST1-FINALLY', function() {
        fnext();
      });
    }, function() {});

});
/*
   ここまで test code
 */
</script>
</body>
</html>

あとがき

というわけで、control-structuresをご紹介しつつ、DeferredやPromiseを使わない、もっと伝統的な見た目の非同期制御構造を見てきましたが、いかがでしょうか。

可読性というのは、習慣によっても変わってきます。要は「何に慣れているか」ということです。Haskellが可読性が高いかどうか、PHPが可読性が高いかどうか。前者は一度分かれば明晰ですがそれまでが大変、後者は一度分かるまでは簡単ですが意外な(場合によってはお節介な)言語機能が介在していてよく裏切られる、という特徴があります。既にDeferredに慣れ親しみまくっている人は、「可読性良いじゃん」と思うかも知れません。ただ、直交性はないと思うのです。それと似た記述の制御構造を持つ言語は少ないですよね。

可読性やタイプ数やプログラミング速度などにおいて、control-structuresが、皆様のコーディングの一助となれば幸いです。たった4Kbで、コードの持つ本質的な運命支援作用により、運命の彼氏と結ばれるかも知れません(ただし、MITライセンスなので無保証です)。npmはこちら。GitHubはこちらです。

15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15