forEach에서 async await를 사용할 때 결과값이 순차적으로 오지 않는 상황이 발생했는데 이에 대한 해결법과 원인에 대해서 알아보자
환경
- Javascript
- async and await
- Python
- Flask
들어가기에 앞서
- 간단하게 async await에 대해 정리하고 테스트할 환경을 정의한다.
async await
async
- 비동기 함수를 정의하는 키워드
- 하단의
await
를 사용하기 위해 붙여줘야합니다.
await
Promise
객체를 기다리며async
함수안에서 사용 가능합니다.Promise
가resolve
되거나reject
될 때까지 기다립니다.- 쉽게 설명해 비동기 요청에 대한 응답이 올때까지 기다리는 키워드라고 보시면 됩니다.
예제
function timer(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function test() {
var x = await timer(10); // wait 2000 milliseconds
console.log(x); // 10
}
test();
테스트 환경
- 클라이언트에서 본인의 ID를 보내서 그 ID값을 그대로 반환하게 환경을 구축
- 클라이언트는 자바스크립트로 서버는 파이썬으로 했으나 테스트 용도이기에 서버측은 몰라도 이해에 문제는 없습니다.
테스트 구조
서버 코드
from flask import Flask, request
import json
import time
app = Flask(__name__)
host_addr = "0.0.0.0"
port_num = "8080"
@app.route("/test", methods = ['POST'])
def test():
print("Time:" + str(int(round(time.time() * 1000))) + ", reqeust from client : " + str(request.json['data']))
return str(request.json['data'])
if __name__ == "__main__":
app.run(host = host_addr, port = port_num)
문제
forEach
를 사용 시 순차적으로 요청 응답이 오지 않는다.
요청 코드
arr = [1, 2, 3, 4];
arr.forEach(async (i) => {
const response = await axios.post("http://0.0.0.0:8080/test", {
data: i,
});
console.log("forEachRequestUsingAsyncAwait server response: " + response.data);
});
응답
- 응답이 순서대로 오지 않았다.
$ node test.js
forEachRequestUsingAsyncAwait server response: 1
forEachRequestUsingAsyncAwait server response: 4
forEachRequestUsingAsyncAwait server response: 3
forEachRequestUsingAsyncAwait server response: 2
서버측 로그
- 순서대로 요청이 오지 않았다.
Time:1602409922301, reqeust from client : 1
127.0.0.1 - - [11/Oct/2020 18:52:02] "POST /test HTTP/1.1" 200 -
Time:1602409922302, reqeust from client : 4
127.0.0.1 - - [11/Oct/2020 18:52:02] "POST /test HTTP/1.1" 200 -
Time:1602409922303, reqeust from client : 3
127.0.0.1 - - [11/Oct/2020 18:52:02] "POST /test HTTP/1.1" 200 -
Time:1602409922303, reqeust from client : 2
127.0.0.1 - - [11/Oct/2020 18:52:02] "POST /test HTTP/1.1" 200 -
이유
이유
- forEach는 배열에 대해 Callback을 순차적으로 실행하기 때문이다. Callback을 순차적으로 실행하지만 Callback이 하나가 끝나기까지 기다렸다가 다음이 실행되는게 아니기 때문이다.
- 반복문처럼 하나가 완료되면 다음 순서에 따라 진행되는게 아니라 각각을 콜백으로 실행하기 때문이다.
공식문서 자료
Array.prototype.forEach ( callbackfn [ , thisArg ] )
- ECMAScript Language Specification에 보면
forEach
는 “callbackfn
should be a function that accepts three arguments.forEach
callscallbackfn
once for each element present in the array, in ascending order.callbackfn
is called only for elements of the array which actually exist; it is not called for missing elements of the array.”라고 나와있다.
arr.forEach(callback(currentValue[, index[, array]]) {
// execute something
}[, thisArg]);
- MDN Web Docs에 보면
forEach
는 “forEach()
calls a providedcallback
function once for each element in an array in ascending order.”라고 나와있다.
해결방법
for-in 혹은 for-of 사용
for-in
- 요청 코드
async function forInRequest () {
for (const i in arr){
const response = await axios.post("http://0.0.0.0:8080/test", {
data: arr[i],
});
console.log("forInRequest server: " + response.data);
}
}
forInRequest();
- 응답 결과
node test.js
forInRequest server: 1
forInRequest server: 2
forInRequest server: 3
forInRequest server: 4
- 서버 응답 결과
Time:1603631271374, reqeust from client : 1
127.0.0.1 - - [25/Oct/2020 22:07:51] "POST /test HTTP/1.1" 200 -
Time:1603631271383, reqeust from client : 2
127.0.0.1 - - [25/Oct/2020 22:07:51] "POST /test HTTP/1.1" 200 -
Time:1603631271386, reqeust from client : 3
127.0.0.1 - - [25/Oct/2020 22:07:51] "POST /test HTTP/1.1" 200 -
Time:1603631271388, reqeust from client : 4
127.0.0.1 - - [25/Oct/2020 22:07:51] "POST /test HTTP/1.1" 200 -
for-of
- 요청 코드
async function forOfRequest () {
for (const i of arr){
const response = await axios.post("http://0.0.0.0:8080/test", {
data: i,
});
console.log("forOfRequest server: " + response.data);
}
}
forOfRequest();
- 응답 결과
node test.js
forOfRequest server: 1
forOfRequest server: 2
forOfRequest server: 3
forOfRequest server: 4
- 서버 응답 결과
Time:1603631202328, reqeust from client : 1
127.0.0.1 - - [25/Oct/2020 22:06:42] "POST /test HTTP/1.1" 200 -
Time:1603631202335, reqeust from client : 2
127.0.0.1 - - [25/Oct/2020 22:06:42] "POST /test HTTP/1.1" 200 -
Time:1603631202338, reqeust from client : 3
127.0.0.1 - - [25/Oct/2020 22:06:42] "POST /test HTTP/1.1" 200 -
Time:1603631202342, reqeust from client : 4
127.0.0.1 - - [25/Oct/2020 22:06:42] "POST /test HTTP/1.1" 200 -
참고자료
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
- https://tc39.es/ecma262/#sec-array.prototype.foreach
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
- https://tc39.es/ecma262/#sec-for-in-and-for-of-statements