На Хабре было уже довольно много статей про новые возможности стандарта ECMAScript 6 (например,«Отказываемся от коллбэков: Генераторы в ECMAScript 6») и многие используют эти возможности.
Используя примеры из приведенной статьи, мы можем:
1. Легко написать любой итератор, например, итератор по дереву
2. Написать псевдо-синхронный код (борьба с callback)
Но что делать, если нам надо написать рекурсивный итератор по дереву, а получение дочерних узлов требует вызова асинхронной функции с передачей callback?
Простой рекурсивный итератор (без вызова асинхронных функций) мог бы выглядеть довольно просто и кратко:
Приведенный выше пример прост, поскольку использует синхронный вызов функции getChildren. Если же функция getChildren асинхронная и требует передачи callback (например, это получение данных с диска или по сети) – то все существенно усложняется. Мы уже не можем так просто написать (следуя примерам из приведенной выше статьи)
В этом случае функция iterTree остановится в месте вызова оператора yield и передаст управление вызывающей функции. Но у нас рекурсивная функция – и вызывающая функция тоже передаст управление выше (оператор yield *). В результате пользователь нашего итератора будет получать узлы дерева, перемежаемые неожиданными значениями.
Можно конечно написать специальные коды внутри той функции, которая использует этот итератор, чтобы различать возвращаемые значения итератора и возвращаемые значения при вызове асинхронных функций. Можно также запоминать путь от корня до текущей вершины (фактически создав структуру вроде стека) и использовать этот путь в callback функции. Но в любом случае код становится довольно сложным – по крайней мере перестает быть легко читаемым.
И все же, можно ли написать простой и читабельный итератор обхода дерева, требующий вызова асинхронных функций?
Несколько удивительно, но новые возможности языка прекрасно сочетаются со старым добрым node-fiber. Существует множество библиотек, основанных на node-fiber, например, wait.for или node-sync.
Например, используя wait.for, можно написать код, практически идентичный простому итератору, приведенному в начале статьи:
Используя node-sync можно получить аналогичный результат.
Совместное использование новых возможностей ES6 и node-fiber (а также библиотек, на их основе) позволяет существенно упростить коды.
Иногда объединение альтернативных подходов дает удивительный результат.
Используя примеры из приведенной статьи, мы можем:
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 (а также библиотек, на их основе) позволяет существенно упростить коды.
Иногда объединение альтернативных подходов дает удивительный результат.