Имплементация coroutine в NodeJS

от автора

Выход iojs сподвиг меня на изучение функций, которые уже стали стабильными в v8, в частности нативным промисам и генераторам, которые можно превращать в корутины. Удивился, что на Хабре нету статьи посвященной тому как самому сделать еще одну имплементацию coroutine через генераторы и понять, что же на самом деле происходит в co/bluebird. Чтобы начать пользоваться без страха перед магией coroutine прошу под кат.

Корутины(coroutine) — это функция(программа) имеющая несколько входных точек. Ее можно остановить в определенной точке, выполнить что-то другое, а затем снова вернуться в эту точку и продолжить с новыми данными(необязательно с новыми данными но с тем же состоянием, что и было). Fiber является одной из имплементацией корутин для nodejs(например node-fibers), но сейчас мы говорим об имплементации без написания плагина на C++ или метапрограммирования, только нативными методами Javascript.

В частности корутины нам дают возможность писать подобный код:

function timeout(ms, msg){     return new Promise(function(resolve, reject){         setTimeout(function(msg){             console.log(msg);             resolve(msg);         }, ms, msg);     });  }   var makeJob = function *(){     yield timeout(1000, 1);     yield timeout(1000, 2);     var user = yield getUser();     return user.info; }; generatorWrapper(makeJob); 

Получается довольно кратко и понятнее. Вместо timeout может быть любая другая функция умеющая в promise’ы, например ходящая за данными в базу и возвращающая данные прямо в эту же функцию, а не в then/callback, что довольно удобно, т.к. позволяет и отменять исполнение последовательных асинхронных функций более удобно, чем это возможно в promise chain.
Вся суть вопроса как выглядит этот generatorWrapper. И это мои рассуждения на этот счет.

function async(gen){     var instance = gen(); //создаем инстанс генератора, по которому будем итерировать     return new Promise(function(resolve, reject){ //функция обертка для приходящих данных из корутины         function next(r){             if (r.done)             {                 resolve(r.value);//если все закончилось просто отдаем resolve'им результат генератора.             }             if (typeof(r.value.then)=='function')//проверка через duck typing, т.к. есть много Promise совместимых библиотек             {                 r.value.then(function(someRes){                     next(instance.next());//вызываем next снова на новых данных.                 }, function(e){                     reject(e);                 });             }             else             {                 console.log(r.value);                 next(instance.next());             }         }         next(instance.next());//отдаем результат из генератора в функцию обертка     }); } 

Эта имплементация идеологическая для понимания как оно все работает, но идеология примерно такая же в co и bluebird.
Конечно полноценными имплементациями является co и bluebird.coroutine, в них есть паралельная обработка и возможность расширенной обработки в том числе работа с другими генераторами.

По коду видно, что генератор в JS, хоть и позволяет писать код вида как корутины, но на самом деле не предназначен для этого. Генератор, извините за капитанство и тавтологию, прежде всего генератор и наиболее удобно на нем все таки делать ленивые вычисления, а использование его подобным образом не совсем его прямое назначение, хотя по моим тестам никаких деградации производительности не заметил.
С другой стороны становится ясно глядя на будущие стандарты типа await/async, что все в принципе не так уж далеко, уже текущими корутинами на базе генераторов можно спокойно пользоваться используя iojs + co/bluebird не опасаясь за стабильность продукта. А учитывая, что все правильные имплементации корутин возвращают нативный promise то все это вполне совместимо с грядущими стандартами.

Резюмирую: спокойно ставьте и используйте это в iojs. В код написанный на промисах все это встает без каких либо изменений. Если что-то хочется добавить к функционалу можете поглядеть в тот же co и дописать что-то свое, там нет ничего страшного в этой обработке. Если хочется сделать async race не отказывайте себе в этом, но придется тогда это делать в другой функции на promise’ах, не генераторе, или придумать свой формат данных, в остальном feel free с генераторами, они теперь такая же часть стандарта как и все остальное.

P.S. Извините за сырой материал, надеюсь на вашу помощь по его улучшению.

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