Javascript 안티 패턴 #2

작업 경험에 따른 지양하는 코드에 관한 이야기


Javascript 안티 패턴은 현재 총 2편의 시리즈로 구성되어 있습니다.

  1. Javascript 안티 패턴 #1
  2. Javascript 안티 패턴 #2 (현재글)

들어가며

이번편에서는 반복 구문에 대한 특징(차이)을 알아보려고 합니다.

구문 예시

반복 구문에는 for문, forEach(), for in, for of가 있습니다.

// for문
for (let i = 0; i < array.length; i++);

// forEach()
array.forEach((value, index) => { /* ... */ })

// for in
for (let index in array)

// for of
for (const value of array)



구문 개요

배열에 값을 출력할 때, 배열 요소 or 인덱스를 통해 접근하는 것으로 나눌 수 있습니다.

  • 인덱스로 접근: for문, for in
// javascript
const array = ['a', 'b', 'c'];

for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

for (let index in array) {
  console.log(array[index]);
}

  • 요소(value)로 접근: forEach(), for of
// javascript
array.forEach((value, index) => {
  console.log(value);
});

for (const value of array) {
  console.log(value);
}

👉 개요에서는 값을 출력하기 위한 방법으로 반복 구문을 구분할 수 있습니다.



숫자가 아닌 속성(key 값)에 대한 결과

배열의 타입은 array지만 객체(object) 범주에 있습니다. (typeof를 해보면 object로 출력되는 것으로 알 수 있죠)

const array = ['a', 'b', 'c'];

typeof array; // 'object'

따라서 배열의 요소(value)를 추가할 때, 객체에서 key와 value를 추가하는 방식을 동일하게 사용할 수 있는데, 이것은 숫자가 아닌 문자열로 속성을 추가 할 수 있다는 것을 의미합니다.

// 배열의 요소 추가
array['index3'] = 'd'; // array.index3 = 'd'
// or
array[3] = 'd';

그러나 추가는 할 수 있지만 출력은 생각처럼 되지 않을 수 있습니다. 왜냐하면 4개의 구문 중에서 오직 for in만 문자열 속성으로 추가한 요소를 출력하기 때문입니다.

array['index3'] = 'd';

// for in
for (let i in array) {
  console.log(array[i]);
};
// 출력 'a', 'b', 'c', 'd'

*****************************************************************************
// for문/ forEach()/ for of
for (let i = 0; i < array.length; ++i) {
  console.log(array[i]);
};

array.forEach((value) => console.log(value));

for (const value of array) {
  console.log(value);
};
// 출력 'a', 'b', 'c'

또한, 숫자 속성으로 요소를 추가했을 때도 차이를 보입니다. 숫자 4로 추가하였을 때, for in과 forEach()에서는 바로 다음 index의 요소로, for문과 for of index 4의 요소로 추가됩니다.

array[4] = 'd';

// for in/ forEach()
for (let i in array) {
  console.log(array[i]);
};

array.forEach((value) => console.log(value));
// 출력 'a', 'b', 'c', 'd'

*****************************************************************************
// for문/ for of
for (let i = 0; i < array.length; ++i) {
  console.log(array[i]);
};

for (const value of array) {
  console.log(value);
};
// 출력 'a', 'b', 'c', undefined, 'd'

👉 따라서 배열 순환 시, 속성(key 값)으로 요소를 추가했거나 속성에 대한 내용이 확실하지 않은 경우 배열 반복 구문의 사용을 지향해야 합니다. (Object.keys(array), Object.values(array) 활용)

forEach()의 비동기적 특징

forEach()는 주어진 배열에 각 요소를 callback으로 실행하는데 비동기적 처리 방식을 사용할 수 있습니다.


(동기/ 비동기에 대한 자세한 내용은 아래 포스팅을 참고 해주세요.)

배열을 순서대로 0.2초씩 요소를 출력하기 위해 아래와 같은 코드를 만들었습니다. 그러나 예상과는 다른 결과를 가져옵니다.

async function print(n) {
  await new Promise((resolve) => setTimeout(() => resolve(), 1000 - n * 200));
  console.log(n, 1000 - n * 200);
}

async function test() {
  let array = [0, 1, 2, 3, 4];
  await array.forEach(print);
}

test();
// 4, 3, 2, 1, 0을 출력

forEach()는 배열의 요소를 callback을 하지만, 함수가 실행을 끝난 뒤 callback(callback은 함수의 실행이 끝난 뒤 실행됨)을 하고 있지 않기 때문입니다.
문제를 해결하기 위해서 아래 asyncForEach() 함수가 많이 사용되고 있는데 비동기(async) 함수와 기다린(await) callback, 그리고 for문(for of도 사용 가능)을 통해 구현된 코드 입니다.

async function print(n) {
  await new Promise((resolve) => setTimeout(() => resolve(), 1000 - n * 200));
  console.log(1000 - n * 200);
  // console.log(n);
}

//
async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

async function test() {
  let array = [0, 1, 2, 3, 4];

  asyncForEach(array, async (n) => {
    await print(n);
  });
}

test();
// 0, 1, 2, 3, 4을 출력

👉 forEach는 callback 함수이나 callback을 기다려주지 않기 때문에 비동기로 사용할 때 주의해야 하며 관련 코드에 대한 참고가 필요합니다.



마치며

반복 구문에 대한 내용은 이 주제를 쓰기 위한 궁극적인 이유였는데요. 포스팅(학습)을 하면서 ‘어렵다’를 느낀 주제이기도 하였습니다. 단순히 반복 구문이 반복 성향에 치우친 것이 아니라는 것과 연관된 문법(object, async 등)이 상당하는 것, 그리고 이런 부분을 쉽게 쓰기 위한 포스팅 작업 때문이었습니다. 한편으로는 반복 구문에서 비동기 처리에 대한 내용에 대한 학습의 필요성을 느끼기도 하여 추후 좀 더 상세한 내용을 가지고 포스팅해 보도록 하겠습니다.
고맙습니다. - 끄읕 -

참고문서


추천 글