非同期再帰イテレーターとNode.jsでのコールバックとの戦い

HabréにはECMAScript 6標準の新機能(たとえば、 「コールバックの拒否:ECMAScript 6のジェネレーター」 )に関するかなり多くの記事が既にあり、多くはこれらの機能を使用しています。



上記の記事の例を使用すると、次のことができます。

1.ツリーイテレータなどのイテレータは簡単に記述できます

2.擬似同期コードを作成します(コールバックとの戦い)



しかし、ツリー上で再帰イテレータを作成する必要があり、子ノードを取得するにはコールバックで非同期関数を呼び出す必要がある場合はどうでしょうか?



(非同期関数を呼び出さずに)単純な再帰イテレーターは、非常に単純で簡潔に見える可能性があります。



function* iterTree(treenode) { var children = getChildren(treenode); if (children) { // inner node for (let i=0; i < children.length; i++) { yield* iterTree(children[i]); // (*) recursion } } else { // leaf node yield treenode; } }
      
      





上記の例は、getChildren関数の同期呼び出しを使用するため、単純です。 getChildren関数が非同期で、コールバックが必要な場合(たとえば、ディスクまたはネットワーク経由でデータを受信する場合)、すべてがはるかに複雑になります。 これを簡単に書くことはできません(上記の記事の例を参照)



 let children = yield getChildren(treenode, resumecallback);
      
      





この場合、iterTree関数はyieldステートメントの呼び出しの場所で停止し、呼び出し元の関数に制御を渡します。 しかし、再帰関数があります-呼び出し関数は上記の制御も渡します(yield *演算子)。 その結果、イテレータのユーザーは、予期しない値が散在するツリーノードを受け取ります。



もちろん、この反復子を使用する関数内に特別なコードを記述して、非同期関数を呼び出すときに反復子の戻り値と戻り値を区別することができます。 また、ルートから現在の頂点までのパスを覚えて(実際にスタックのような構造を作成する)、このパスをコールバック関数で使用することもできます。 しかし、いずれにしても、 コードは非常に複雑になります -少なくとも読みやすくなります。



それでも、非同期関数の呼び出しを必要とする単純で読みやすいツリートラバーサルイテレータを作成することは可能ですか?



驚くべきことに、この言語の新しい機能は古き良きノードファイバーと完全に組み合わされていますwait - fornode-syncなど、node-fiberに基づいた多くのライブラリがあります。

たとえば、 wait.forを使用すると、記事の冒頭に示した単純なイテレータとほぼ同じコードを作成できます。



 function* iterTree(treenode) { var children = wait.for(getChildren, treenode); //  if (children) { // inner node for (let i=0; i < children.length; i++) { yield* iterTree(children[i]); // (*) recursion } } else { // leaf node yield treenode; } }
      
      





node-syncを使用すると、同様の結果を得ることができます。



ES6の新機能とノードファイバー(およびそれらに基づくライブラリ)を併用すると、コードを大幅に簡素化できます。



代替アプローチを組み合わせると、驚くべき結果が得られる場合があります。



All Articles