2017-05-29 29 views
1

將參數傳遞給ES6生成器的next()時,爲什麼忽略第一個值?更具體地說,爲什麼這個輸出說x = 44而不是x = 43ES6生成器機制 - 傳遞給next()的第一個值會在哪裏?

function* foo() { 
    let i = 0; 
    var x = 1 + (yield "foo" + (++i)); 
    console.log(`x = ${x}`); 
} 

fooer = foo(); 

console.log(fooer.next(42)); 
console.log(fooer.next(43)); 

// output: 
// { value: 'foo1', done: false } 
// x = 44 
// { value: undefined, done: true } 

我對這種發電機的行爲心智模式是這樣的:

  1. 回報foo1和屈服暫停(和所述next呼叫返回foo1取作爲參數42
  2. 暫停,直到下一個呼叫到next
  3. 旁邊產量繼續到符合var x = 1 + 42,因爲這是論據先前接收
  4. 打印x = 43
  5. 距離最後next返回{done: true},忽略它的參數(43)和停止。

現在很明顯,這不是發生了什麼事情。所以... 我在這裏錯了什麼?

+0

調用'FOO()'不執行,直到第一個'yield',它僅創建發電機對象。第一個'next()'調用從函數的頂部開始執行,直到第一個「yield」 - 並返回在那裏產生的值。 – Bergi

回答

3

我寫了這樣的代碼,以更徹底地調查行爲(在重新...之後...)。-reading的MDN docs on generators):

function* bar() { 
    pp('in bar'); 
    console.log(`1. ${yield 100}`); 
    console.log(`after 1`); 
    console.log(`2. ${yield 200}`); 
    console.log(`after 2`); 
} 
let barer = bar(); 
pp(`1. next:`, barer.next(1)); 
pp(`--- done with 1 next(1)\n`); 
pp(`2. next:`, barer.next(2)); 
pp(`--- done with 2 next(2)\n`); 
pp(`3. next:`, barer.next(3)); 
pp(`--- done with 3 next(3)\n`); 

它輸出這樣的:

in bar 
1. next: { value: 100, done: false } 
--- done with 1 next(1) 

1. 2 
after 1 
2. next: { value: 200, done: false } 
--- done with 2 next(2) 

2. 3 
after 2 
3. next: { value: undefined, done: true } 
--- done with 3 next(3) 

因此很明顯,正確的心智模式會是這樣的:

  • 在第一次調用next,發電機函數體運行到yield表達式時,返回yield100第一次)的「參數」作爲值由next返回,和所述發電機主體是暫停之前評估產量表達的值 -部分是至關重要的「之前」的

  • 只到next第二呼叫是所述第一yield表達式計算/與給下一個上調用的參數的值替代的值(不與以前一個給定爲我期望的一個),以及執行運行,直到第二yield,和next返回此第二屈服的參數的值 - 這裏是我的錯誤:我以爲第一yield表達的值是第一次調用到next的說法,但實際上它的參數next,或另一種方式的第二個電話把它,它的通話到next的說法,其執行過程中的價值實際上是計算

這可能對誰來發明更有意義,因爲next的調用次數是yield語句的數量的一倍(還有最後一個返回信號終止的{ value: undefined, done: true }),所以如果第一次調用的參數不會是忽略,那麼最後一次呼叫就不得不被忽略。另外,在評估下一個主體時,替換將從其調用的之前的的參數開始。這將是更加直觀恕我直言,但我相信它是關於按照慣例在其他語言也和一致性是在最後的最好的事情發電機...


題外話,但啓發:只是試圖在Python中做同樣的探索,它顯然實現了類似於Javascript的生成器,當試圖將第一次調用傳遞給next()(清楚地表明我的心智模型錯了!​​)時,我立即得到了TypeError: can't send non-None value to a just-started generator,並且迭代器API也會以拋出StopIteration異常結束,因此不需要「額外」next()來檢查done是真的(我想用這個額外的電話副作用,利用最後一個下一個參數只會導致很難難以理解和調試代碼...)。比JS更容易「饒舌」......

+1

是的,這就是它的工作原理。從前面的「下一個」調用中拿出參數是沒有意義的,就好像它被緩衝了一樣。它不會立即「迴應」傳遞的價值。 – Bergi

+1

由於發生器實現了來回(或進出)傳遞「消息」,因此總是需要從某處開始。我想,通過讓第一個產出的價值無處不在,也可能有比「下一個」調用更多的「yield」聲明,但在更常見的情況下,這只是醜陋的。 – Bergi

+0

@Bergi啊,這是有道理的,我根本沒有考慮到「響應性」......猜測「對傳遞給下一個參數的參數值的可能反應」**是直觀的如果我以這種方式想到它,那麼發生在*實際*呼叫期間! ......但在傳遞參數給第一次調用next命令時拋出一個錯誤,就像我看到的Python一樣,會幫助像我這樣的新手變成一個loooot :) – NeuronQ

2

我從Axel Rauschmayer's Exploring ES6得到了這個,尤其是22.4.1.1。

在收到.next(arg)後,發電機的第一個動作是將arg送到yield。但在第一次調用.next()時,沒有yield來接收這個消息,因爲它只是在執行結束時。

僅在第二次調用x = 1 + 43時被執行並隨後被記錄,並且生成器結束。

+0

這個解釋很有趣。我認爲我自己最終得到了答案,看到答案,你的解釋和文檔對我來說似乎都不夠明確:「將」arg「提供給」產出「或」不產出「來接收這個」甚至意味着什麼?!我認爲根據正式的評估模型來考慮代碼,「替代模型」(https://mitpress.mit.edu/sicp/full-text/sicp/book/node10.html)對我來說是最簡單的理解,而不是在「餵食」,「發送」,「接收」等方面......這些話對我來說沒有多大意義,除非我在談論多個過程...... – NeuronQ

0

也很難將我的頭圍繞在發電機上,特別是當投入if語句取決於所產生的值時。儘管如此,if語句實際上是什麼幫助我得到它在最後:

function* foo() { 
    const firstYield = yield 1 
    console.log('foo', firstYield) 

    const secondYield = yield 3 
    console.log('foo', secondYield) 

    if (firstYield === 2) { 
    yield 5 
    } 
} 

const generator = foo() 

console.log('next', generator.next(/* Input skipped */).value) 
console.log('next', generator.next(2).value) 
console.log('next', generator.next(4).value) 

/* 
    Outputs: 

    next 1 
    foo 2 
    next 3 
    foo 4 
    next 5  
*/ 
相關問題