[Selenium-웹자동화] async Iterator 유의사항 ( findElements By.css )
안녕하세요 셀레니움 JS를 쓰다보면 단순하게 문제가 되는 부분이 있습니다.
모든 API 들이 전부 비동기 방식이기 때문인데요
A,B,C의 WebElement를 가져올건데
findElements 로 가져오게되면
Promise <Pending> (Array) 로 가져오게 됩니다.
만약에 가져와서 A,B,C에 대한 작업을 순서대로 해야한다면?
로 가져온 후에 (사실 가져오는 예시도 await가 붙어있어 망정이지
그냥 let webElements = driver.findElements(By.css('.webElements')) 로 하게되면 이 또한 동작하지 않습니다.
Promise<pending> 값이 나옵니다.
순환하면서 작업한다고 할시
webElements.map( (item,index)=>{
});
webElements.forEach( (item,index)=>{
});
결과는???
X 동작하지않습니다. driver.findElements 의 반환값이 Object이기 때문이죠
프로토타이핑을 합시다.
Array.prototype.map.call(webElements, (item,index) =>{
});
Array.prototype.forEach.call(webElements,(item,index) =>{
});
결과는?
넵 동작합니다. 하지만 돌려보시면 알겠지만 문제가 하나 생깁니다.
console.log("Before");
Array.prototype.map.call(webElements, (item,index) =>{
// do thing
});
console.log("Start");
시에 해당 작업의 결과는 Before -> Start -> do thing 이 됩니다.
아 ! 안에서 async 하게 해줘야 되는데 그쵸?
console.log("Before");
await Array.prototype.map.call(webElements, async (item,index) =>{
// await do thing
});
console.log("Start");
결과는요??
Before -> Start - > await do thing 입니다.
왜인가 하면
async 함수와 await는 잘 썻습니다
하지만 이 반복 자들이 매번 반복 할때마다 반환되는 Promise에 대한 then 처리가 없기 때문이에요
함수를 async 하게 해서 내부적으로 await 하는건 내부적으로 반환된 item 에 대한 비동기 API 사용시 해당 결과를
then 하는 것일 뿐 실제로 저 반환되는 item에 대한 Promise가 없기 때문이죠 (프로미!)
해결법이 있습니다.
ES9 에서 나온 해결법 부터 말씀드리겠습니다.
for await of 문입니다.
console.log("Before"); for await (const item of webElements){ // do thing [item] } console.log("Start");
결과는요??
Before -> await do thing -> Start 입니다.
드디어 잘나오네요 for await of문은 비동기 Iterator 를 위해서 매 순환마다 Promise로 받기 때문에
순서 보장 및 동기적 보장을 해줍니다. 하지만 ES9기준이니 Node에서는 10버전 부터 사용 가능합니다.
reduce를 활용합니다.
console.log("Before"); await Array.prototype.reduce(webElements,async (acc,item,index)=>{ const nextItem = await acc; return nextItem; },Promise.resolve()); console.log("Start");
리듀스의 특징 중 순환 마다 누적값?을 받아오고 초기값을 지정할수 있다는 점이 있는데요
이 특징을 이용해서 초기값을 Promise.resolve() 로 받게 됩니다. 그러면 첫번째 초기값을 리졸브로 시작하며
인덱스를 받게 되는데
그떄 await로 받으면 (then 으로 받아도 됩니다.)
webElement, (acc,item,index)=>{
acc.then( (nextItem)=>{
})
.catch( (err)=>{console.error(err);};
},Promise.resolve());
와 같은 느낌으로 받을수도있지만 결국 또 콜백 헬;;;
그렇기 때문에 위의 async await를 쓰도록 합시다.
위의 상황을 다시 한번 말씀드리면 첫 인덱스는 Promise.resolve 로 받게 되고
인덱스 아이템이 넘어오는 Promise를 await acc 로 기다리면서 받습니다. (동기적)
그리고 그 내부 코드에서 비동기 코드는 다시 await를 쓰고 아닌경우는 평소와같이 씁니다.
그리고 결과 값을 다시 누적하기 위해 던져줍니다.
reduce에 대한 자세한 설명은 밑의 블로그에 있는데
정말 설명이 끝내줍니다.
// 제로초 블로그
https://www.zerocho.com/category/JavaScript/post/5acafb05f24445001b8d796d