[Javascript] forEach에서 async await가 순차적으로 수행되지 않는 경우

forEach에서 async await를 사용할 때 결과값이 순차적으로 오지 않는 상황이 발생했는데 이에 대한 해결법과 원인에 대해서 알아보자


환경

  • Javascript
  • async and await
  • Python
  • Flask


들어가기에 앞서

  • 간단하게 async await에 대해 정리하고 테스트할 환경을 정의한다.

async await

async

  • 비동기 함수를 정의하는 키워드
  • 하단의 await를 사용하기 위해 붙여줘야합니다.

await

  • Promise 객체를 기다리며 async 함수안에서 사용 가능합니다.
  • Promiseresolve되거나 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 calls callbackfn 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 provided callback 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 -


참고자료