Map, Set, WeakMap, WeakSet 지원
Map
키 : 값 쌍
- 생성법 : const m = new Map();
- 메소드
- set / get / has / delete / size / clear
- keys : 키 배열 반환
- values : 값 배열 반환
- entries : <키:값> 배열 반환
Set
중복 허용 X
- 생성법 : const s = new Set();
- 메소드
- add / size / delete ...
WeakMap / Set
기존 맵/셋과의 차이점
- 키 값 : 객체만 가능
- 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 Weak Map/Set 에서 자동으로 삭제됨
- 반복 작업과 키나 값 전체를 얻는게 불가능(size, keys(), values(), entries ... etc)
- why ? 가비지 컬렉션 동작 시점이 불 분명
UseCase
추가 데이터 : 부차적인 데이터 저장시
외부 코드에 속한 객체를 가지고 작업을 한다. 이 객체에 데이터를 추가해줘야 하는 데, 추가해줄 데이터는 객체가 살아있는 동안에만 유효하다.
위크맵은 복잡한 데이터를 저장하고, 위크셋은 단순한 데이터(예/아니오)를 저장하기에 좋다.
- 사용자의 방문횟수 세기
- Map
// visitsCount.js
let visitsCountMap = new Map();// 맵에 사용자의 방문 횟수를 저장
// 방문 횟수 카운트
function conntUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
// main.js
// john 방문시
let john = { name: "John" };
// 방문 횟수 증가
countUser(john);
// john의 방문 횟수를 셀 필요가 없어지면 null로 덮어씁니다.
john = null;
-
- WeakMap
// visitsCount.js
let visitsCountMap = new WeakMap();// 위크맵에 사용자의 방문 횟수를 저장
// 방문 횟수 카운트
function conntUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
// main.js
// john 방문시
let john = { name: "John" };
// 방문 횟수 증가
countUser(john);
//john 객체가 가비지컬렉션의 대상이되면, 자동으로 메모리에서 삭제됩니다.
- 사용자의 사이트 방문 여부를 추적(WeakSet)
let visitSet = new WeakSet();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); // John 방문
visitedSet.add(pete); // ㅖete 방문
visitedSet.add(john); // John 방문
// 방문 여부 확인
alert(visitedSet.has(john)); // true
alert(visitedSet.has(mary)); // false
john = null; // visitSet에서 john을 나타내는 객체가 자동으로 삭제
캐싱
- 함수 연산 결과를 맵에 저장
- Map
// cache.js
let cache = new Map();
// 연산을 수행하고 그 결과를 맵에 저장
function process(obj) {
if(!cache.has(obj)) {
let result = /* 연산 수행 */obj;
cache.set(obj, result);
}
return cache.get(obj);
}
// main.js
// 함수 process() 호출\
let obj = {/* ... 객체 ... */};
let result1 = process(obj); // 연산 수행
let result2 = process(obj); // 연산 수행을 하지않고 저장된 결과 반환
// 객체가 쓸모없어지면 null로 덮어씀
obj = null;
alert(cache.size); // 1 (객체가 cache에 남아 있음. 메모리 낭비)
-
- WeakMap
// cache.js
let cache = new WeakMap();
// 연산을 수행하고 그 결과를 맵에 저장
function process(obj) {
if(!cache.has(obj)) {
let result = /* 연산 수행 */obj;
cache.set(obj, result);
}
return cache.get(obj);
}
// main.js
// 함수 process() 호출\
let obj = {/* ... 객체 ... */};
let result1 = process(obj); // 연산 수행
let result2 = process(obj); // 연산 수행을 하지않고 저장된 결과 반환
// 객체가 쓸모없어지면 null로 덮어씀
obj = null;
// size를 사용할 수 없지만, obj가 키로 캐싱된 데티어는 메모리에서 삭제됨
Promise
패턴
대표적인 패턴
- all([...]) : 여러 프라미스를 동시에 편성(복수의 병렬/동시 작업)하여 모두 이루어진다는 전제로 동작
- race([...]) : 결승선을 통과한 최초의 프라미스만 인정하고 나머지는 무시
- 하나라도 이루어진 프라미스가 있을 경우에 이루어지고 하나라도 버려지는 프라미스가 있으면 버려진다.
all과 race를 변형한 패턴 중에 자주 쓰이는 것들이 있다.
- none([]) : all과 비슷하지만 이룸/버림이 정반대다. 모든 프라미스는 버려져야 하며, 버림이 이룸값이 되고 이룸이 버림값이 된다.
- any([]) : all과 유사하나 버림은 모두 무시하며, 전부가 아닌 하나만 이루어지면 된다.
- first([]) : race와 비슷하다. 최초로 프라미스가 이루어지고 난 이후엔 다른 이룸/버림은 간단히 무시한다.
- last([]) : first와 거의 같고 최후의 이룸 프라미스 하나만 승자가 된다는 것만 다르다.
var getTimePromise = (sec) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(sec);
resolve(`Wow! ${sec}second`);
}
, sec * 1000);
});
}
var one = getTimePromise(1);
var two = getTimePromise(2);
// 여러 프라미스를 동시에 편성(복수의 병렬/동시 작업)하여 모두 이루어진다는 전제로 동작
/*Promise.all([one, two]) // all에 전달하는 배열은 프라미스,thenable, 즉시값 모두 가능
.then((msgs) => {
// all이 반환한 메인 프라미스는 자신의 하위 프라미스들이 모두 이루어져야 이루어질 수 있다.
console.log(msgs);
})*/
// 결승선을 통과한 최초의 프라미스만 인정하고 나머지는 무시
// 하나라도 이루어진 프라미스가 있을 경우에 이루어지고 하나라도 버려지는 프라미스가 있으면 버려진다
Promise.first([one, two])
.then((msgs) => {
console.log(msgs);
})
/* Promise.race([])를 이용한 프라미스 타임아웃 패턴
추가 설명 : then() 메서드는 Promise (en-US)를 리턴하고 두 개의 콜백 함수를 인수로 받습니다. 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수입니다.
Promise.race([one, timeoutPromise( 3000 )])
.then(() => {
function(){
// `foo(..)`가 제때 이루어졌다.
},
function(err){
// `foo()`가 버려졌거나 제때 마치지 못했으니
// `err`를 조사하여 원인을 찾는다.
}
})*/
async & await
목적 : 좀 더 코드를 짜기 쉽게
- 프로미스를 간결하고 간편하게 좀 더 동기적으로 실행되는 것처럼 보여준다.
- 프로미스 체이닝을 하면 코드가 난잡해질 수 있는 데 이 방법을 사용하면 동기식으로 코드를 순서대로 작성하는 것처럼 간편하게 할수잇게 도와준다.
- 기존에 존재하는 것을 감싸서 좀더 간편하게 사용할 수 잇는 api를 제공하는 것은 syntactic sugar라고 합니다.
- 무조건 async & await를 사용하라는 건 아니다. async&await / promise 중 패턴에 따라 편한 걸로 사용하면 된다.
정의
async
promise를 리턴하는 함수를 좀 더 간편하게 정의 가능
- 프로미스가 리턴되기 때문에 .then() 사용 가능
async function myAsync() {
return 'async';
}
const res = myAsync();
console.log(res); // Promise{<resolve>: "async"}
res.then((result) => {
// then의 리턴값 : result
console.log(result); // "async"
});
await
프로미스가 resolve되서 결과 값이 넘어올 때까지 기다린다.
- await는 async함수 내부에만 사용 가능
- 그냥 함수면 일반 함수처럼 리턴된다.
예외 처리
예외 발생 : throw
예외 처리 : catch
- async함수내 await 되는 프로미스 함수가 reject되면 자동으로 throw 발생 : try-catch / .catch
async function wait(ms) {
// return new Promise (resolve => setTimeout(resolve, ms));
throw 'error';
}
console.log(wait(3)); // Promise{<rejected>: "error"}
async function useTryCatch() {
console.log(`in useTryCatch : ${new Date()}`);
try {
await wait(3); // throw error!
} catch(e) {
console.error(e); // 에러 잡기
}
console.log(`in useTryCatch : ${new Date()}`);
}
async function useCatchFun() {
console.log(`in useCatchFun : ${new Date()}`);
const result = await wait(3).catch(e => {
console.error(e);
}); // catch를 통해 리턴된 프라미스를 await : 정상적이면 resolve값 반환 / 예외 발생 catch에서 리턴한 값. 이렇게 없으면 undefined
console.log(`in useCatchFun : ${new Date()}`);
}
async function all() { // 순차 처리
await useTryCatch();
await useCatchFun();
}
all();
패턴
위의 프로미스 패턴 사용 가능(all, race)
- 병렬 처리
async function wait(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
async function getApple() {
await wait(1000);
return 'apple';
}
async function getBanana() {
await wait(1000);
return 'banana';
}
async function parallel() { // 순차 처리
return Promise.all([getApple(), getBanana()]).then(fruits => {
console.log(fruits);
return fruits.join(` + `);
});
}
parallel().then((res) => {
console.log(res);
});
참고
Generator
목적 : Promise + Generator => async & await
개념
문법
function *foo(x) {
var y = x * (yield "y value:");
return y;
}
var it = foo( 6 );
// start `foo(..)`
it.next(); // {value:"y value:", done: false}
var res = it.next( 7 ); // {value:42, done: true}
- 추가 입/출력 메시징 기능 존재
- yield;
- 함수 실행 멈춤
- 함수 실행 재개 명령이 올때까지 기다림
- next()
- 파라미터를 통해 멈춘 yield위치에 값을 전달
- 함수 실행 재개
- yield;
- 함수에서 사용되는 argument를 이용한 input, return을 이용한 output 가능
- 그 외 기능
- return() : Generator를 종료시킨다.
- throw() : Generator의 실행을 재개시키고 Generator 함수의 실행 문맥 속으로 error를 주입한다.
Interleaving
이터레이터를 생성할 때마다 이터레이터가 제어할 제너레이터의 인스턴스를 암시적으로 생성한다.
동일한 제너레이터의 여러 인스턴스를 동시에 실행 가능하며 상호 작용할수도 있다.
function *foo() {
var x = yield 2;
z++;
var y = yield (x * z);
console.log( x, y, z );
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 <-- yield 2
var val2 = it2.next().value; // 2 <-- yield 2
val1 = it1.next( val2 * 10 ).value; // 40 <-- x:20, z:2
val2 = it2.next( val1 * 5 ).value; // 600 <-- x:200, z:3
it1.next( val2 / 2 ); // y:300
// 20 300 3
it2.next( val1 / 4 ); // y:10
// 200 10 3
다른 두 제너레이터 함수의 반복자를 각각 어떤 순서로 부르느냐에 따라 다른 결과를 생성할 수 있다.
iterable, iterator와의 관계
제너레이터는 이터레이터를 좀 더 쉽게 사용가능하게 한다.
- iterable, iterator 개념 : https://ko.javascript.info/iterable
function* foo() {
yield 1;
yield 2;
yield 3;
return;
}
let generator = foo();
for(let value of generator) {
alert(value); // 1, 2, 3가 출력됨
}
Generator Delegation(위임)
- yield * [반복 가능한 객체]
- 재귀, 비동기, 예외 등 대부분의 동작에 대한 위임이 가능하다.
function *foo(val) {
console.log(val);
if (val > 1) {
// generator recursion
val = yield *foo( val - 1 );
}
}
const gen = foo(3);
gen.next();
예제
1, 4, 9, 16, ... n^2 값을 반환하는 함수 : 제너레이터에 변수를 넣으면 함수를 계속실행해도 그 값이 유지됩니다.
function *getSquaredNumber(max) {
let n = 0; // 다음 메소드를 계속 호출하면서 그 가치를 유지하고 있습니다.
while(n < max) {
n++;
yield n * n;
}
}
// usecase 1
const res = getSquaredNumber(3);
console.log(res.next().value);
console.log(res.next().value);
console.log(res.next().value);
console.log(res.next().value);
// usecase 2
for(const n of getSquaredNumber(3)) {
console.log(n);
}
배열에서 중복 없이 무작위로 이름을 선택하는 함수(한번 선택되면 그 후에는 선택되지 않음)
function *getUniqueRandomVal(array) {
const available = array;
while(available.length !== 0) {
const randomIdx = Math.floor(Math.random() * available.length);
const val = available[randomIdx];
// remove the used value from the list of available values
available.splice(randomIdx, 1);
console.log(available);
yield val;
}
}
const names = ["aa", "bb", "cc", "dd"];
for(const name of getUniqueRandomVal(names)) {
console.log(name);
}
메모리를 적게쓰면서 간단한게 사용하는 법(내장 함수 reverse(), filter()...)
const arr = [1, 2, 3, 4];
// case 1 : 메모리 사용량 = 원본 배열 X 2
console.log("[case 1]");
for(const val of arr.reverse()) { // 이 함수는 이 함수를 호출한 배열을 거꾸로 뒤집고, 그 배열을 가리키는 참조값을 반환합니다. 따라서, 이 함수를 실행시키면 원본 배열이 변형됩니다.
console.log(val);
}
// case 2 : 메모리 사용량 = 원본 배열
const arr2 = [1, 2, 3, 4];
console.log("[case 2]");
for(let i = arr2.length - 1; i >= 0; i--) {
console.log(arr2[i]);
}
// case 3 : 메모리 사용량 = 원본 배열, use generator
console.log("[case 3]");
function *reverse(array) {
for(let i = array.length - 1; i >= 0; i--) {
yield array[i];
}
}
for(const val of reverse(arr2)) {
console.log(val);
}
async & await와 Promise + Generator 비교
참조
'JS' 카테고리의 다른 글
Class 내 arrow function은 사용하지 않아야한다. (0) | 2022.07.22 |
---|