2015-01-12 52 views
22

循環索引(i)不是我期望的,當我在一個循環內使用量角器。使用量角器與循環

症狀:

失敗:索引超出界限。試圖在索引來訪問元素:「X」,但只有「X」元素

指數是靜態的,總是等於最後一個值

我代碼

for (var i = 0; i < MAX; ++i) { 
    getPromise().then(function() { 
    someArray[i] // 'i' always takes the value of 'MAX' 
    }) 
} 

例如:

var expected = ['expect1', 'expect2', 'expect3']; 
var els = element.all(by.css('selector')); 
for (var i = 0; i < expected.length; ++i) { 
    els.get(i).getText().then(function(text) { 
    expect(text).toEqual(expected[i]); // Error: `i` is always 3. 
    }) 
} 

var els = element.all(by.css('selector')); 
for (var i = 0; i < 3; ++i) { 
    els.get(i).getText().then(function(text) { 
    if (text === 'should click') { 
     els.get(i).click(); // fails with "Failed: Index out of bound. Trying to access element at index:3, but there are only 3 elements" 
    } 
    }) 
} 

var els = element.all(by.css('selector')); 
els.then(function(rawelements) { 
    for (var i = 0; i < rawelements.length; ++i) { 
    rawelements[i].getText().then(function(text) { 
     if (text === 'should click') { 
     rawelements[i].click(); // fails with "Failed: Index out of bound. Trying to access element at index:'rawelements.length', but there are only 'rawelements.length' elements" 
     } 
    }) 
    } 
}) 
+1

感謝您的努力 - 但這是經典的閉環問題。 –

+0

@BenjaminGruenbaum是的,這是經典的閉環問題,我在答案中引用了http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example。但是,我打開這個有兩個原因。 1)很多人沒有意識到兩者之間的相關性,因爲有些人不理解elementFinder返回的承諾,2)閉合不是量角器的最佳解決方案,因爲有量角器特定的解決方案 - 請參閱答案 – hankduan

+2

懸念是殺了我!有兩個原因? –

回答

31

出現這種情況的原因是因爲量角器使用的承諾。

https://github.com/angular/protractor/blob/master/docs/control-flow.md

承諾(即element(by...)element.all(by...))執行他們的功能then當基礎值變爲就緒。這意味着所有的承諾都會首先安排好,然後在結果準備就緒時運行then函數。

當你運行像這樣:

for (var i = 0; i < 3; ++i) { 
    console.log('1) i is: ', i); 
    getPromise().then(function() { 
    console.log('2) i is: ', i); 
    someArray[i] // 'i' always takes the value of 3 
    }) 
} 
console.log('* finished looping. i is: ', i); 

什麼情況是,立即getPromise().then(function() {...})回報,之前的承諾已準備就緒,而不執行then裏面的功能。所以首先循環運行3次,調度所有的getPromise()調用。然後,當承諾解決時,運行相應的then

控制檯會是這個樣子:

1) i is: 0 // schedules first `getPromise()` 
1) i is: 1 // schedules second `getPromise()` 
1) i is: 2 // schedules third `getPromise()` 
* finished looping. i is: 3 
2) i is: 3 // first `then` function runs, but i is already 3 now. 
2) i is: 3 // second `then` function runs, but i is already 3 now. 
2) i is: 3 // third `then` function runs, but i is already 3 now. 

那麼,你如何在循環運行量角器? 一般的解決方案是關閉。見JavaScript closure inside loops – simple practical example

for (var i = 0; i < 3; ++i) { 
    console.log('1) i is: ', i); 
    var func = (function() { 
    var j = i; 
    return function() { 
     console.log('2) j is: ', j); 
     someArray[j] // 'j' takes the values of 0..2 
    } 
    })(); 
    getPromise().then(func); 
} 
console.log('* finished looping. i is: ', i); 

但是這不是很好的閱讀。幸運的是,您還可以使用量角器功能filter(fn),get(i),first(),last(),以及expect已修補以承諾承諾的事實來處理此問題。

回到前面提供的例子。第一個例子可以被重寫爲:

var expected = ['expect1', 'expect2', 'expect3']; 
var els = element.all(by.css('selector')); 
for (var i = 0; i < expected.length; ++i) { 
    expect(els.get(i).getText()).toEqual(expected[i]); // note, the i is no longer in a `then` function and take the correct values. 
} 

第二和第三個實施例可以被改寫爲:

var els = element.all(by.css('selector')); 
els.filter(function(elem) { 
    return elem.getText().then(function(text) { 
    return text === 'should click'; 
    }); 
}).click(); 
// note here we first used a 'filter' to select the appropriate elements, and used the fact that actions like `click` can act on an array to click all matching elements. The result is that we can stop using a for loop altogether. 

換句話說,量角器有許多方法來迭代或接入元件i以便你不不需要使用循環和i。但是,如果您必須使用循環和i,則可以使用閉包解決方案。

+2

在這裏看到過這個問題好幾次了,謝謝你清理東西!現在我們可以 – alecxe

+0

是的,我在過去一週看到過這個確切的問題兩次,還有很多其他的承諾相關的問題,希望它能幫助人們更多地瞭解一般的承諾 – hankduan

+1

「循環計數器「問題不是特定於承諾*在for循環中定義的任何函數都將成爲計數器終端值的受害者,無論它是否爲承諾回調函數,請考慮例如事件處理程序 - 相同的處理。 –

2

漢克在回答這個問題上做得很好。
我想也注意到另一個快速和骯髒的方式來處理這個問題。只需將承諾內容移至某個外部函數並將其傳遞給索引。

例如,如果你想登錄頁面上的所有列表項的(從ElementArrayFinder)各自的指數,你可以做這樣的事情:

var log_at_index = function (matcher, index) { 
    return $$(matcher).get(index).getText().then(function (item_txt) { 
     return console.log('item[' + index + '] = ' + item_txt); 
    }); 
    }; 

    var css_match = 'li'; 
    it('should log all items found with their index and displayed text', function() { 
    $$(css_match).count().then(function (total) { 
     for(var i = 0; i < total; i++) 
     log_at_index(css_match, i); // move promises to external function 
    }); 
    }); 

這是在方便的時候,你需要做一些快速調試&容易調整爲您自己的使用。