简体   繁体   English

如何使用基于回调的循环的yield?

[英]How to use yield with callback based loops?

Although the primary purpose of the yield keyword is to provide iterators over some data, it is also rather convenient to use it to create asynchronous loops: 虽然yield关键字的主要目的是为某些数据提供迭代器,但使用它来创建异步循环也相当方便:

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

This can then be called asynchronously: 然后可以异步调用它:

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);
}

However, in modern javascript, callback based loops have become quite popular. 但是,在现代javascript中,基于回调的循环已经变得非常流行。 We have Array.prototype.forEach , Array.prototype.find or Array.prototype.sort . 我们有Array.prototype.forEachArray.prototype.findArray.prototype.sort All of these are based on a callback passed for each iteration. 所有这些都基于每次迭代传递的回调。 I even heard it to be recommended that we use these if we can, because they can be optimized better than standard for loops. 我甚至听说建议我们尽可能使用这些,因为它们可以比循环的标准更好地进行优化。

I also often use callback based loops to abstract away some complex looping pattern. 我还经常使用基于回调的循环来抽象出一些复杂的循环模式。

And the question here is, is it possible to turn those into yield based iterators? 这里的问题是,是否有可能将这些转化为基于yield的迭代器? As a simple example, consider I wanted you to sort an array asynchronously. 举个简单的例子,我想让你以异步方式对数组进行排序。

tl;dr: You can't do that, but check out this other thing you can do with the latest V8 and bluebird : tl;博士:你不能这样做,但看看你可以用最新的V8和蓝鸟做的其他事情:

 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); } 

No, it's not possible to make Array.prototype.sort accept the results of comparisons asynchronously from its comparison function; 不,不可能使Array.prototype.sort从其比较函数异步接受比较结果; you would have to reimplement it entirely. 你必须完全重新实现它。 For other individual cases, there might be hacks, like a coroutiney forEach (which doesn't even necessarily work as you'd expect, because every generator will run until its first yield before one continues from a yield ): 对于其他个别情况,可能存在黑客攻击,例如一个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));
        })
    );
}

and reduce , which works nicely by nature (until you look at performance): 并且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);
}

but yeah, no magic wand for all functions. 但是,没有魔杖适用于所有功能。

Ideally, you'd add alternate promise-based implementations for all these functions – popular promise libraries, like bluebird, already do this for map and reduce , for example – and use async / await instead of generators (because async functions return promises): 理想情况下,您需要为所有这些函数添加备用的基于promise的实现 - 流行的promise库,比如bluebird,已经为mapreduce执行此操作,例如 - 并使用async / await而不是generator(因为async函数返回promises):

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

    console.log(sum);
}

You wouldn't need to wait for async support to do this so much if ECMAScript had sane decorators like Python, either: 如果ECMAScript具有像Python这样的理智装饰器,你就不需要等待async支持来做这么多:

@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);
}

… but it doesn't and so you do. ......但事实并非如此。 Or you can live with code like this: 或者你可以使用这样的代码:

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);
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM