2015-01-12 1 views
22

Loop-Index (i) ist nicht das, was ich erwarte, wenn ich den Winkelmesser in einer Schleife verwenden.Verwenden von Winkelmesser mit Schleifen

Symptome:

fehlgeschlagen: aus gebundenen Index. Der Versuch Element bei Index zuzugreifen: 'x', aber es gibt nur 'x' Elemente

oder

Index ist statisch und immer gleich dem letzten Wert

Meine Code

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

Zum Beispiel:

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

oder

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

oder

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

Danke für die Mühe - aber das ist das klassische Closure-Loop-Problem. –

+0

@BenjaminGruenbaum Ja das ist das klassische Closure-Loop-Problem, und ich verweisen http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example in der Antwort. Allerdings habe ich das aus zwei Gründen geöffnet. 1) viele Menschen nicht erkennen, die Korrelation zwischen den beiden, weil einige Menschen nicht verstehen ElementFinders Versprechen zurück und 2) Schließung ist nicht die beste Lösung für Winkelmesser als es gibt Winkelmesser spezifische Lösungen dafür - siehe Antwort – hankduan

+2

Die Spannung ist bringt mich um! Welche zwei Gründe? –

Antwort

31

Der Grund, warum dies geschieht, ist, weil Winkelmesser Versprechen verwendet.

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

Promises (d.h. element(by...), element.all(by...)) führen ihre then Funktionen, wenn der Basiswert bereit wird. Was das bedeutet ist, dass alle Versprechen zuerst geplant werden und dann die then Funktionen ausgeführt werden, wie die Ergebnisse bereit werden.

Beschreibung Wenn Sie so etwas wie dieses:

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

Was passiert, ist, dass getPromise().then(function() {...}) kehrt sofort, bevor das Versprechen bereit ist, und ohne die Funktion innerhalb des then ausführt. Also läuft die Schleife zuerst dreimal durch und plant alle getPromise() Anrufe. Dann, als die Versprechen sich auflösen, werden die entsprechenden then s ausgeführt.

Die Konsole würde wie folgt aussehen:

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. 

Also, wie Sie laufen Sie Winkelmesser in Schleifen? Die allgemeine Lösung ist Schließung. Siehe 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); 

Aber das ist nicht so schön zu lesen. Glücklicherweise können Sie auch Winkelmesserfunktionen verwenden filter(fn), get(i), first(), last(), und die Tatsache, dass expect gepatcht ist, um Versprechen zu nehmen, damit umzugehen.

Zurück zu den oben angegebenen Beispielen.Das erste Beispiel umgeschrieben werden kann als:

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. 
} 

Die zweite und dritte Beispiel kann wie folgt umgeschrieben werden:

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. 

Mit anderen Worten, hat Winkelmesser viele Möglichkeiten iterieren oder Zugriffselement i, so dass Sie don 't müssen for-Schleifen und i verwenden. Aber wenn Sie For-Schleifen und i verwenden müssen, können Sie die Verschlusslösung verwenden.

+2

Habe dieses Problem schon mehrmals hier gesehen, danke fürs Aufräumen! Jetzt können wir Beziehen Sie sich auf diesen Beitrag. – alecxe

+0

Yup, ich habe dieses genaue Problem zweimal in der vergangenen Woche und viele andere Versprechen-bezogenen Fragen gesehen.Hoffentlich wird es Leuten helfen, Versprechungen im Allgemeinen etwas mehr zu verstehen. – hankduan

+1

Der "Schleifenzähler "Problem ist nicht spezifisch für Versprechen. * Jede * -Funktion, die in einer for-Schleife definiert ist, wird Opfer des terminalen Wertes des Zählers, egal ob es sich um einen Versprechensrückruf handelt oder nicht. Betrachten wir zum Beispiel einen Ereignishandler - dasselbe Geschäft. –

2

Hank hat einen tollen Job bei der Beantwortung dieses Problems gemacht.
Ich wollte auch einen anderen schnellen und schmutzigen Weg, um dies zu behandeln. Verschieben Sie einfach das Versprechen Zeug zu einer externen Funktion und übergeben Sie den Index.

Zum Beispiel, wenn Sie alle Listenelemente auf der Seite an ihrem jeweiligen Index (von ElementArrayFinder) anmelden wollen könnten Sie so etwas tun:

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

Das ist praktisch, wenn Sie einige tun müssen, schnelles Debugging & einfach für Ihren eigenen Gebrauch zwicken.