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

Tom*_*ica 6 javascript arrays asynchronous yield

尽管yield关键字的主要目的是提供某些数据的迭代器,但使用它来创建异步循环也相当方便:

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

然后可以异步调用:

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);
}
Run Code Online (Sandbox Code Playgroud)

然而,在现代 JavaScript 中,基于回调的循环已经变得相当流行。我们有Array.prototype.forEachArray.prototype.findArray.prototype.sort。所有这些都基于每次迭代传递的回调。我什至听说有人建议我们尽可能使用它们,因为它们可以比标准 for 循环更好地优化。

我还经常使用基于回调的循环来抽象出一些复杂的循环模式。

这里的问题是,是否有可能将它们变成yield基于迭代器?作为一个简单的示例,考虑我希望您对数组进行异步排序。

Ry-*_*Ry- 2

\n

tl;dr:您可以\xe2\x80\x99t 执行此操作,但请查看您可以使用最新的 V8 和bluebird执行的其他操作执行的其他操作:

\n\n
async function asyncReduce() {\n    const sum = await Promise.reduce(\n        [1, 2, 3, 4, 5],\n        async (m, n) => m + await Promise.delay(200, n),\n        0\n    );\n\n    console.log(sum);\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

不,\xe2\x80\x99s 不可能Array.prototype.sort从其比较函数异步接受比较结果;你将不得不完全重新实现它。对于其他个别情况,可能存在黑客行为,例如协程forEach(它甚至不一定像您期望的那样工作,因为每个生成器都会运行到第一个生成器之前,然后yieldyield):

\n\n
function syncForEach() {\n    [1, 2, 3, 4, 5].forEach(function (x) {\n        console.log(x);\n    });\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n\n\n
function delayed(x) {\n    return new Promise(resolve => {\n        setTimeout(() => resolve(x), Math.random() * 1000 | 0);\n    });\n}\n\nfunction* chain(iterators) {\n    for (const it of iterators) {\n        yield* it;\n    }\n}\n\nfunction* asyncForEach() {\n    yield* chain(\n        [1, 2, 3, 4, 5].map(function* (x) {\n            console.log(yield delayed(x));\n        })\n    );\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

reduce,本质上效果很好(直到您查看性能):

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

但是,是的,没有万能的魔杖。

\n\n

理想情况下,您\xe2\x80\x99d 为所有这些函数添加基于承诺的替代实现\xe2\x80\x93 流行的承诺库,如bluebird,已经为map和执行此操作reduce,例如\xe2\x80\x93 并使用async/await代替发电机(因为async函数返回 Promise):

\n\n
async function asyncReduce() {\n    const sum = await Promise.reduce(\n        [1, 2, 3, 4, 5],\n        async (m, n) => m + await delayed(n),\n        0\n    );\n\n    console.log(sum);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

你\xe2\x80\x99不需要等待async如果 ECMAScript 有像 Python 这样健全的装饰器,

\n\n
@Promise.coroutine\nfunction* add(m, n) {\n    return m + (yield delayed(n));\n}\n\n@Promise.coroutine\nfunction* asyncReduce() {\n    const sum = yield Promise.reduce([1, 2, 3, 4, 5], add, 0);\n\n    console.log(sum);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6 但它没有\xe2\x80\x99t,所以你这样做了。或者你可以接受这样的代码:

\n\n
const asyncReduce = Promise.coroutine(function* () {\n    const sum = yield Promise.reduce([1, 2, 3, 4, 5], Promise.coroutine(function* (m, n) {\n        return m + (yield delayed(n));\n    }), 0);\n\n    console.log(sum);\n});\n
Run Code Online (Sandbox Code Playgroud)\n