2015-01-12 3 views
22

Циклический индекс (i) - это не то, что я ожидаю, когда я использую Транспортер в цикле.Использование транспортира с петлями

Симптомы:

Ошибка: индекс из границы. Попытка получить доступ к элементу по индексу: «х», но есть только элементы «х»

или

индекс является статическим и всегда равно последнему значению

Мои код

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) многие люди не понимают взаимосвязи между ними, потому что некоторые люди не понимают, что элементы elementFinders возвращают обещания и 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

Yup, я видел эту проблему дважды на прошлой неделе и много других вопросов, связанных с обещаниями. Надеюсь, это поможет людям понять обещания в целом немного больше. – hankduan

+1

«loop counter» «проблема не относится к обещаниям». Любая функция *, определенная в цикле 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 
    }); 
    }); 

Это очень удобно, когда вам нужно сделать некоторые быстрая отладка & легко настраивается для вашего собственного использования.

Смежные вопросы