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

в 21:57, , рубрики: coroutines, generators, iojs, javascript, node.js, 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. Извините за сырой материал, надеюсь на вашу помощь по его улучшению.

Автор: KlonD90

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js