이번 포스팅에선 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 |