안녕하세요 셀레니움 JS를 쓰다보면 단순하게 문제가 되는 부분이 있습니다.

 

모든 API 들이 전부 비동기 방식이기 때문인데요

 

A,B,C의 WebElement를 가져올건데

 

findElements 로 가져오게되면

 

Promise <Pending> (Array) 로 가져오게 됩니다. 

 

만약에 가져와서 A,B,C에 대한 작업을 순서대로 해야한다면?

 

let webElements = await driver.findElements(By.css('.webElements'))

 

로 가져온 후에   (사실 가져오는 예시도 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

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts