の非同期発電機とパイプライン



非同期発電機の導入
この記事と最後の1つは、非同期イテレータを扱っていますが、いくつかのプログラミングをしていたときに発生した問題によって動機づけられましたasync 機能:それは可能ですかyield インasync 機能?言い換えると、我々はasync 関数は、発電機の機能ですか?
この問題を調査するために、通常の同期発電機関数から始めましょう.numberGenerator :
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min

const getValue = () => {
    return random(1,10)
}

const numberGenerator = function* () {
    for (let i=0; i<5; i++) {
        const value = getValue() 
        yield value**2
    }
}

const main = () => {
    const numbers = numberGenerator()
    for (const v of numbers) {
        console.log('number = ' + v)
    }
}

main()
このコードは5つの乱数の予想される正方形を生成します.
C:\dev>node gen.js
number = 1
number = 64
number = 36
number = 25
number = 49
私の考えは変更することでしたgetValue 約束を返すnumberGenerator to await この約束はyield 値.次のように試しました.
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min

const getValue = () => {
    //return promise instead of value
    return new Promise(resolve=>{
        setTimeout(()=>resolve(random(1,10)), 1000)
    })
}

const numberGenerator = function* () {
    for (let i=0; i<5; i++) {
        const value = await getValue() //await promise
        yield value**2
    }
}

const main = () => {
    const numbers = numberGenerator()
    for (const v of numbers) {
        console.log('number = ' + v)
    }
}

main()
何が起こるか見ましょう
C:\dev\gen.js:12
                const value = await getValue() //await promise
                              ^^^^^

SyntaxError: await is only valid in async function
    at new Script (vm.js:51:7)
さて、それは意味をなす.我々は我々を作る必要があるnumberGenerator 機能async . 試してみましょう!
const numberGenerator = async function* () { //added async
仕事ですか.
C:\dev\gen.js:10
const numberGenerator = async function* () { //added async
                                      ^

SyntaxError: Unexpected token *
    at new Script (vm.js:51:7)
ああ、それは動作しませんでした.これは、私が何かをオンラインでいくつかのトピックを検索する主導です.この種の機能性があると判明released in ES2018 , そして、私たちはそれを使用することができます--harmony-async-iteration フラグ.
アクションでこれを見ましょう.
const timer = () => setInterval(()=>console.log('tick'), 1000)

const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min

const getValue = () => {
    //return promise instead of value
    return new Promise(resolve=>{
        setTimeout(()=>resolve(random(1,10)), 1000)
    })
}

const numberGenerator = async function* () { //added async
    for (let i=0; i<5; i++) {
        const value = await getValue() //await promise
        yield value**2
    }
}

//main is 'async'
const main = async () => {
    const t = timer()
    const numbers = numberGenerator()

    //use 'for await...of' instead of 'for...of'
    for await (const v of numbers) {
        console.log('number = ' + v)
    }

    clearInterval(t)
}

main()
コードの以前のバージョンからいくつかの小さな変更があります.
  • The main 関数のfor...of ループになるfor await...of ループ.
  • 我々は使用しているのでawait , main としてマークasync
  • A timer was also added so we can confirm that the generator is indeed asynchronous.


    結果を見てみましょう.
    C:\dev>node --harmony-async-iteration gen.js
    tick
    number = 16
    tick
    number = 1
    tick
    number = 100
    tick
    number = 100
    tick
    number = 49
    
    それは働いた!

    The yield in an async generator function is similar to the yield in a normal (synchronous) generator function. The difference is that in the regular version, yield produces a {value, done} tuple, whereas the asynchronous version produces a promise that resolves to a {value, done} tuple.

    If you yield a promise, the JavaScript runtimes does something a bit sneaky: It still produces its own promise that resolves to a {value, done} tuple, but the value attribute in that tuple will be whatever your promise resolves to.



    パイプライン同期非同期発電機
    この技術のきちんとした小さなアプリケーションを見てみましょう:我々は非同期の発電機関数を作成します.
    この種のパイプラインは、非同期データストリームの任意の変換を実行するために用いることができる.
    最初に、値の無限のストリームを生成する非同期発電機を記述します.毎秒0と100の間にランダムな値を生成します.
    const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
    
    const asyncNumberGenerator = async function* () {
        while (true) {
            const randomValue = random(0,100)
    
            const p = new Promise(resolve=>{
                setTimeout(()=>resolve(randomValue), 1000)
            })      
    
            yield p
        }
    }
    
    次に関数を書きます.createStatsReducer . この関数はコールバック関数を返します.exponentialStatsReducer , これはデータのストリームを繰り返し計算するために使用されます.
    const createStatsReducer = alpha => { 
        const beta = 1 - alpha
    
        const exponentialStatsReducer = (newValue, accumulator) => {
            const redistributedMean = beta * accumulator.mean
    
            const meanIncrement = alpha * newValue
    
            const newMean = redistributedMean + meanIncrement
    
            const varianceIncrement = alpha * (newValue - accumulator.mean)**2
    
            const newVariance = beta * (accumulator.variance + varianceIncrement)
    
            return {
                lastValue: newValue,
                mean: newMean,
                variance: newVariance
            }
        }
    
        return exponentialStatsReducer
    }
    
    次に、第二の非同期発電機関数があります.asyncReduce . これは非同期iterableに還元器を適用します.JavaScriptの組み込みのように動作します Array.prototype.reduce . しかし、標準バージョンは、最終的な値を生成するために、配列全体を通過しますが、我々のバージョンは、lazily削減を適用します.これにより、データソースとして無限の値のシーケンス(上記の非同期数発生器)を使用できます.
    const asyncReduce = async function* (iterable, reducer, accumulator) {
        for await (const item of iterable) {
            const reductionResult = reducer(item, accumulator)
    
            accumulator = reductionResult
    
            yield reductionResult
        }
    }
    
    いっしょに結びましょう.以下のコードは、非同期に生成された数の無限のシーケンスを、非同期の削減にパイプします.新しい値が到着すると、更新された平均、分散、および標準偏差を取得します.
    const timer = () => setInterval(()=>console.log('tick'), 1000)
    
    const main = async () => {
        const t = timer()
    
        const numbers = asyncNumberGenerator()
    
        const firstValue = await numbers.next()
    
        //initialize the mean to the first value
        const initialValue = { mean: firstValue.value, variance: 0 }
    
        console.log('first value = ' + firstValue.value)
    
        const statsReducer = createStatsReducer(0.1)
    
        const reducedValues = asyncReduce(numbers, statsReducer, initialValue)
    
        for await (const v of reducedValues) {
            const lastValue = v.lastValue
            const mean = v.mean.toFixed(2)
            const variance = v.variance.toFixed(2)
            const stdev = Math.sqrt(v.variance).toFixed(2)
    
            console.log(`last value = ${lastValue}, stats = { mean: ${mean}`
                + `, variance: ${variance}, stdev: ${stdev} }`)
        }
    
        clearInterval(t)
    }
    
    main()
    
    いくつかのサンプル出力を見てみましょう.
    C:\dev>node --harmony-async-iteration async_stats.js
    tick
    first value = 51
    tick
    last value = 97, stats = { mean: 55.60, variance: 190.44, stdev: 13.80 }
    tick
    last value = 73, stats = { mean: 57.34, variance: 198.64, stdev: 14.09 }
    tick
    last value = 11, stats = { mean: 52.71, variance: 372.05, stdev: 19.29 }
    tick
    last value = 42, stats = { mean: 51.64, variance: 345.16, stdev: 18.58 }
    tick
    last value = 42, stats = { mean: 50.67, variance: 319.00, stdev: 17.86 }
    tick
    last value = 60, stats = { mean: 51.60, variance: 294.93, stdev: 17.17 }
    ^C
    
    我々は現在、継続的に値の我々の非同期ストリーム上の統計情報を更新取得します.きちんと!
    非同期式の関数は、これらの行に沿った非同期データのソースに対する処理を行うのに特に役立つと思います.
    私はあなたが何を考えるか、またはあなたが他の方法のためのアイデアがある場合は非同期ジェネレータとイテレータを使用することができます教えてください!
    参考文献
  • for await...of
  • ES2018: asynchronous iteration
  • Array.prototype.reduce
  • 関連項目: