2017-02-07 18 views
2

Хотя основная цель ключевого слова доходности является предоставление итераторы по некоторым данным, он также весьма удобно использовать для создания асинхронных циклов:Как использовать доходность с помощью циклов обратного вызова?

function* bigLoop() { 
    // Some nested loops 
    for(...) { 
     for(...) { 
      // Yields current progress, eg. when parsing file 
      // or processing an image       
      yield percentCompleted; 
     } 
    } 
} 

Это можно вызывать асинхронно:

function big_loop_async(delay) { 
    var iterator = big_loop(); 
    function doNext() { 
     var next = iterator.next(); 
     var percent_done = next.done?100:next.value; 
     console.log(percent_done, " % done."); 
     // start next iteration after delay, allowing other events to be processed 
     if(!next.done) 
      setTimeout(doNext, delay); 
    } 
    setTimeout(doNext, delay); 
} 

Однако в современных javascript циклы обратного вызова стали довольно популярными. У нас есть Array.prototype.forEach, Array.prototype.find или Array.prototype.sort. Все они основаны на обратном вызове, который был передан для каждой итерации. Я даже слышал, что рекомендуется использовать их, если можно, потому что они могут быть оптимизированы лучше, чем стандартные для циклов.

Я также часто использую циклы, основанные на обратном вызове, чтобы абстрагироваться от сложного шаблона цикла.

И вопрос в том, можно ли их превратить в итераторы yield? В качестве простого примера рассмотрим, что я хотел, чтобы вы отсортировали массив асинхронно.

+0

Would сортировка массива асинхронно (с помощью генератора и одного потока JS), есть какие-либо льготы? –

+0

@DavinTryon Это пример *, примеры не предназначены для практического применения, но для того, чтобы их было легко понять. В любом случае, массив достаточно длинный, может занять слишком много времени для сортировки и может, например, отключить людей от вашего сервера или браузера пользователя с задержкой. –

+0

@ TomášZato Это [хорошая статья] (http://raganwald.com/2016/05/07/javascript-generators-for-people-who-dont-give-a-shit-about-getting-stuff-done .html), что вы можете найти интересное (неудачное название). Итак, короткий ответ на ваш OP, да, это можно сделать. –

ответ

1

TL; др: Вы не можете сделать это, но проверить эту другую вещь, которую вы можете сделать с последним V8 и bluebird:

async function asyncReduce() { 
    const sum = await Promise.reduce(
     [1, 2, 3, 4, 5], 
     async (m, n) => m + await Promise.delay(200, n), 
     0 
    ); 

    console.log(sum); 
} 

Нет, это не можно сделать Array.prototype.sort асинхронно принимать результаты сравнения из функции сравнения; вам придется полностью реализовать его. Для других отдельных случаях может быть писаки, подобно coroutiney forEach (что не обязательно даже работать, как и следовало ожидать, потому что каждый генератор будет работать до его первого yield перед тем один продолжается с yield):

function syncForEach() { 
    [1, 2, 3, 4, 5].forEach(function (x) { 
     console.log(x); 
    }); 
} 
function delayed(x) { 
    return new Promise(resolve => { 
     setTimeout(() => resolve(x), Math.random() * 1000 | 0); 
    }); 
} 

function* chain(iterators) { 
    for (const it of iterators) { 
     yield* it; 
    } 
} 

function* asyncForEach() { 
    yield* chain(
     [1, 2, 3, 4, 5].map(function* (x) { 
      console.log(yield delayed(x)); 
     }) 
    ); 
} 

и reduce, который хорошо работает по своей природе (пока вы смотрите на производительность):

function syncReduce() { 
    const sum = [1, 2, 3, 4, 5].reduce(function (m, n) { 
     return m + n; 
    }, 0); 

    console.log(sum); 
} 
function* asyncReduce() { 
    const sum = yield* [1, 2, 3, 4, 5].reduce(function* (m, n) { 
     return (yield* m) + (yield delayed(n)); 
    }, function*() { return 0; }()); 

    console.log(sum); 
} 

но да, никакой волшебной палочки для всех функций.

В идеале, вы бы добавить альтернативные реализации обещания на основе всех этих функций - популярные библиотеки обещания, как синяя птица, уже делаете это для map и reduce, например, - и использовать async/await вместо генераторов (потому что async функций возвратные обещания):

async function asyncReduce() { 
    const sum = await Promise.reduce(
     [1, 2, 3, 4, 5], 
     async (m, n) => m + await delayed(n), 
     0 
    ); 

    console.log(sum); 
} 

Вам не нужно будет ждать поддержки async сделать это так много, если ECMAScript был здравомыслящих декораторы, как Python, либо:

@Promise.coroutine 
function* add(m, n) { 
    return m + (yield delayed(n)); 
} 

@Promise.coroutine 
function* asyncReduce() { 
    const sum = yield Promise.reduce([1, 2, 3, 4, 5], add, 0); 

    console.log(sum); 
} 

... но это не так, и вы это делаете.Или вы можете жить с таким кодом:

const asyncReduce = Promise.coroutine(function*() { 
    const sum = yield Promise.reduce([1, 2, 3, 4, 5], Promise.coroutine(function* (m, n) { 
     return m + (yield delayed(n)); 
    }), 0); 

    console.log(sum); 
});