Понимание потока кода с выходами/генераторами

Я прочитал несколько примеров кода с использованием генераторов JavaScript таких как этот. Простейший блок, использующий генератор, который я могу придумать, - это что-то вроде:

function read(path) {
    return function (done) {
        fs.readFile(path, "file", done);
    }
}

co(function *() {
    console.log( yield read("file") );
})();

Это действительно распечатывает содержимое file, но мой зависает там, где вызывается done. По-видимому, выход - это синтаксический сахар для упаковки того, что он возвращает в обратном вызове, и соответствующим образом присваивает значение результата (и, по крайней мере, в случае co, вызывая аргумент ошибки для обратного вызова). Правильно ли я понимаю синтаксис?

Что выглядит done при использовании yield?

Ответ 1

По-видимому, выход - это синтаксический сахар для того, чтобы обернуть то, что он возвращает в обратном вызове, и соответствующим образом присвоить значение результата (и, по крайней мере, в случае co, выбросив аргумент ошибки в обратный вызов)

Нет, yield не является синтаксическим сахаром. Это основной синтаксический элемент генераторов. Когда этот генератор создается, вы можете запустить его (вызывая .next() на нем), и это вернет значение, которое было return ed или yield ed. Когда генератор был yield ed, вы можете продолжить его позже, снова вызвав .next(). Аргументами для next будет значение, возвращаемое выражением yield внутри генератора.

Только в случае co эти асинхронные функции обратного вызова (и другие вещи) обрабатываются "соответственно" для того, что вы считаете естественным в библиотеке потоков асинхронного управления.

Что делается, когда используется доход?

Пример функции thread из статьи которую вы читаете, дает вам хорошее представление об этом:

function thread(fn) {
  var gen = fn();
  function next(err, res) {
    var ret = gen.next(res);
    if (ret.done) return;
    ret.value(next);
  }
  next();
}

В вашем коде yield выводит значение выражения read("file") из генератора при его запуске. Это становится ret.val, результатом gen.next(). Для этого передается функция next - обратный вызов, который будет продолжать генератор с помощью res ult, который был передан ему. В коде генератора выглядит так, как если бы выражение yield возвращало это значение.

"Развернутая" версия происходящего может быть написана следующим образом:

function fn*() {
    console.log( yield function (done) {
        fs.readFile("filepath", "file", done);
    } );
}
var gen = fn();
var ret1 = gen.next();
var callasync = ret1.value;
callasync(function next(err, res) {
    var ret2 = gen.next(res); // this now does log the value
    ret2.done; // true now
});

Ответ 2

Я опубликовал подробное объяснение того, как генераторы работают здесь.

В упрощенной форме ваш код может выглядеть так: co (untested):

function workAsync(fileName)
{
    // async logic
    var worker = (function* () {

        function read(path) {
            return function (done) {
                fs.readFile(path, "file", done);
            }
        }

        console.log(yield read(fileName));
    })();

    // driver
    function nextStep(err, result) {
        try {
            var item = err? 
                worker.throw(err):
                worker.next(result);
            if (item.done)
                return;
            item.value(nextStep);
        }
        catch(ex) {
            console.log(ex.message);
            return;
        }
    }

    // first step
    nextStep();
}

workAsync("file");

Часть драйвера workAsync асинхронно выполняет итерацию через объект-генератор, вызывая nextStep().