본문 바로가기

Backend/Javascript

ES12 (ES2021)

이번 포스팅에선 ES12 의 기능들에 대해 알아보겠습니다.

 

- replaceAll

 

const test1 = '2022-03-03';

const result1 = test1.replace('-', '');
const result2 = test1.replace(/-/g, '');

console.info(result1); // 202203-03
console.info(result2); // 20220303

 

원래 자바스크립트에는 replaceAll 메서드가 없었습니다. 따라서 replace 메서드를 통해 replaceAll 과 같은 효과를 보려면 정규표현식을 같이 사용해야 했습니다.

 

const result1 = test1.replaceAll('-', '');

console.info(result1); // 20220303

const result2 = test1.replaceAll(/-/g, '');

console.info(result2); // 20220303

 

ES12 부터 String.prototype 에 추가된 replaceAll 메서드를 사용할 수 있습니다.

정규표현식과도 함께 사용할 수 있지만 g flag 를 항상 같이 사용해야하며, 없을 경우 TypeError: String.prototype.replaceAll called with a non-global RegExp argument 와 같은 에러가 발생합니다.

 

- Promise.any

 

새로 추가된 Promise.any 메서드는 Promise.all 과 같이 iterable 한 promise 객체를 input 으로 받고, 그 중 가장 먼저 fulfilled 된 promise 를 반환합니다. 예시를 보도록 하겠습니다.

 

const promise1 = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
};

const promise2 = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(2);
    }, 2000);
  });
};

const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(3);
    }, 800);
  });
};

(async() => {
  const results = await Promise.any([promise1(), promise2(), promise3()]);
  console.info(results); // 1
})();

 

setTimeout 의 delay 들을 보면 promise3 가 가장 먼저 결과를 반환하지만 reject 된 promise 이기에 fulfilled 된 promise 를 기다리다가 promise1 의 결과를 반환합니다.

 

const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(3);
    }, 800);
  });
};

(async() => {
  const results = await Promise.any([promise3()]);
  console.info(results);
})();

// [AggregateError: All promises were rejected]

 

만약 주어진 input 의 promise 가 모두 reject 되면 AggregateError 라는 새로운 타입의 에러를 던집니다.

당연히 try - catch 로 컨트롤 할 수 있습니다. 이 에러 타입은 동일하게 ES12 에서 추가된 에러 타입이고, 바로 아래에서 살펴볼 것 입니다.

 

메서드만으론 Promise.allSettled 와 비슷해보이지만 allSettled 는 에러를 던지는 형태가 아니며 fulfilled 와 rejected 를 구분하기 위해선 반환하는 값의 status 를 같이 판별해줘야 합니다. 상황에 따라 Promise.any 를 유용히 쓸 수 있을 것 같습니다.

 

- AggregateError

 

AggregateError 는 Error 의 새로운 subclass 이며 이름 그대로 여러 에러를 하나의 단일 에러로 래핑한 객체입니다.

위의 Promise.any 를 아주 적절한 예시로 사용할 수 있는데, 우선 이 메서드가 왜 필요한지 Promise.all 부터 보겠습니다.

 

const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(3);
    }, 800);
  });
};

const promise4 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(4);
    }, 1000);
  });
};

(async() => {
  try {
    const results = await Promise.all([promise3(), promise4()]);
    console.info(results);
  } catch (e) {
    console.error(e); // 3
  }
})();

 

Promise.all 은 주어진 promise 를 이행하면서 reject 된 결과가 있을 경우 그 사유를 이용해 자신도 거부하는 메서드입니다.

따라서 위의 예시처럼 주어진 2개의 promise 가 거부되어도, catch 절에서는 하나의 사유만 확인할 수 있습니다.

어쩌면 e 파라미터는 단 하나라서 그게 당연한 것 같고, 물론 이 메서드만으로도 프로그래밍 상에서 대부분의 케이스를 커버할 수 있습니다.

 

const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(3);
    }, 800);
  });
};

const promise4 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(4);
    }, 1000);
  });
};

(async() => {
  try {
    const results = await Promise.any([promise3(), promise4()]);
    console.info(results);
  } catch (e) {
    console.error(e); // [AggregateError: All promises were rejected]
    console.info(e instanceof AggregateError); // true
    console.info(e.message); // All promises were rejected
    console.info(e.name); // AggregateError
    console.info(e.errors); // [3, 4]
  }
})();

 

위에서 Promise.any 는 주어진 모든 promise 가 reject 된 경우 AggregateError 를 던진다고 했습니다.

promise3, promise4 가 전부 reject 된 상황에서 위처럼 wrapping 된 하나의 단일 에러를 던져주고, errors 에서 거부된 모든 사유를 확인할 수 있습니다.

 

try { 
  throw new AggregateError([
    new Error('status error'),
    new Error('syntax error')
  ], 'aggregate error');
} catch (e) {
  console.info(e.name); // AggregateError
  console.info(e.message); // aggregate error
  console.info(e.errors); // [ Error: status error..., Error: syntax error... ]
}

 

AggregateError 는 위처럼 직접 첫번째 인자에 여러 에러를 넣어 생성도 가능합니다.

당장은 적절한 활용 예시가 떠오르지 않지만, 일부 배치작업에서 동시에 처리해야하는 promise task 의 실패 사유를 모두 확인해야 할 때 Promise.any 와 조합하여 각 함수 내에서 try - catch 구문을 줄이고 AggregateError 를 로깅할 수 있을 것 같습니다.

 

- logical assignment operators

 

??=, &&=, ||= 이렇게 세가지의 논리 할당 연산자가 추가되었습니다. 하나씩 살펴보겠습니다.

 

  • ??= (The nullish coalescing assignment operator)

기존의 ?? (nullish coalescing operator) 와 합쳐진 할당문입니다. x ??= y 라는 식은 x ?? (x = y) 라는 식과 동일하죠.

 

const test1 = { name: 'jack' };

test1.id ??= 3;
test1.name ??= 'tom';

console.info(test1); // { name: 'jack', id: 3 }

 

test1.id 의 값이 nullish 일 경우 3 을 할당하는 식이고, 동일하게 name 에도 사용했지만 'jack' 이라는 값이 있기 때문에 'tom' 이 할당되진 않았습니다.

 

  • &&= (logical AND assignment operator)

기존의 && (logical AND operator) 와 합쳐진 할당문입니다. x &&= y 식은 x && (x = y) 식과 동일합니다.

 

const test1 = { name: 'jack' };

test1.id &&= 3;
test1.name &&= 'tom';

console.info(test1); // { name: 'tom' }

 

위의 nullish coalescing operator 와는 다르게 값이 있을 때 할당하는 동작입니다. id 는 nullish value 이므로 3 이 할당되지 않았고, name 만 'tom' 으로 재할당됨을 확인할 수 있습니다.

 

  • ||= (logical OR assignment operator)

기존의 || (logical OR operator) 와 합쳐진 할당문입니다. x ||= y 식은 x || (x = y) 식과 동일합니다.

 

const test1 = { name: 'jack' };

test1.id ||= 3;
test1.name ||= 'tom';

console.info(test1); // { name: 'jack', id: 3 }

 

OR 논리 연산자 동작 그대로 id 값이 없으니 3 이 할당된 결과를 볼 수 있습니다.

이 결과만 보면 ??= operator 와 동일한데, 그 차이는 결국 nullish vs falsy value 로 볼 수 있습니다.

 

const test1 = { name: 0 };
const test2 = { name: 0 };

test1.name ??= 1;
test2.name ||= 1;

console.info(test1); // { name: 0 }
console.info(test2); // { name: 1 }

 

원래 연산자가 ?? 는 nullish value (null or undefined) 에 대해, || 는 falsy value 에 대해 동작하므로 위와 같은 차이가 있습니다.

 

- WeakRef

 

WeakRef 객체는 다른 객체에 대해 말 그대로 약한 참조를 유지하며 gc 의 대상이 되는 것을 방지하지 않습니다.

WeakRef 는 Target 또는 Referent 라고 불리는 객체에 대한 약한 참조를 갖고 있으며, 이는 garbage collector 회수 대상에 들어갑니다. 원래 일반 참조는 gc 되지 않고 메모리에 객체가 계속 남아있는데 WeakRef 를 사용하면 gc 대상이 되어 객체를 destory 하고 메모리를 회수할 수 있게 됩니다.

 

마땅한 예시를 들지 못한건 이를 어디에 사용할 수 있을지, 오히려 코드를 읽을 때 더 혼란스러워지지 않을까 싶긴한데..

추후에 가능하면 실무 예시를 들고와보도록 하겠습니다.

 

또한 대상이 gc 되었을 때 동작 가능한 callback 을 등록할 수 있는 FinalizationRegistry, 더 정밀하게 만들어진 Array.prototype.sort 등이 있지만 여기까지만 알아보도록 하겠습니다.

'Backend > Javascript' 카테고리의 다른 글

ES8 (ES2017)  (0) 2022.03.26
ES7 (ES2016)  (0) 2022.03.19
ES11 (ES2020)  (0) 2022.03.05
ES10 (ES2019)  (0) 2022.02.27
ES9 (ES2018)  (0) 2022.02.19