Асинхронный рекурсивный итератор и борьба с callback в Node.js

от автора

На Хабре было уже довольно много статей про новые возможности стандарта ECMAScript 6 (например,«Отказываемся от коллбэков: Генераторы в ECMAScript 6») и многие используют эти возможности.

Используя примеры из приведенной статьи, мы можем
1. Легко написать любой итератор, например, итератор по дереву
2. Написать псевдо-синхронный код (борьба с callback)

Но что делать, если нам надо написать рекурсивный итератор по дереву, а получение дочерних узлов требует вызова асинхронной функции с передачей callback?

Простой рекурсивный итератор (без вызова асинхронных функций) мог бы выглядеть довольно просто и кратко:

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 асинхронная и требует передачи callback (например, это получение данных с диска или по сети) – то все существенно усложняется. Мы уже не можем так просто написать (следуя примерам из приведенной выше статьи)

let children = yield getChildren(treenode, resumecallback); 

В этом случае функция iterTree остановится в месте вызова оператора yield и передаст управление вызывающей функции. Но у нас рекурсивная функция – и вызывающая функция тоже передаст управление выше (оператор yield *). В результате пользователь нашего итератора будет получать узлы дерева, перемежаемые неожиданными значениями.

Можно конечно написать специальные коды внутри той функции, которая использует этот итератор, чтобы различать возвращаемые значения итератора и возвращаемые значения при вызове асинхронных функций. Можно также запоминать путь от корня до текущей вершины (фактически создав структуру вроде стека) и использовать этот путь в callback функции. Но в любом случае код становится довольно сложным – по крайней мере перестает быть легко читаемым.

И все же, можно ли написать простой и читабельный итератор обхода дерева, требующий вызова асинхронных функций?

Несколько удивительно, но новые возможности языка прекрасно сочетаются со старым добрым node-fiber. Существует множество библиотек, основанных на node-fiber, например, wait.for или node-sync.
Например, используя 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 и node-fiber (а также библиотек, на их основе) позволяет существенно упростить коды.

Иногда объединение альтернативных подходов дает удивительный результат.

ссылка на оригинал статьи http://habrahabr.ru/post/257323/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *