Lodash 라이브러리의 Memoize 메서드를 사용하면 이전에 진행했던 연산 결과를 재사용할 수 있다. 실시간 검색창을 구현할 때 입력한 키워드에 대한 API 호출을 시도하는데, 이미 검색했던 키워드는 결과를 캐싱해놓고 재사용하면 API 중복 호출을 방지할 수 있다. 이때 Lodash의 Memoize를 활용할 수 있다(물론 키워드에 대한 결과값이 자주 변한다면 캐싱 기능을 사용할 필요 없다)
import _ from 'lodash';
export const requestQuotes = _.memoize(async title => {
const res = await fetch(`https://animechan.vercel.app/api/quotes/anime?title=${title}`)
if(res.status !== 200) return [];
const quotesArray = await res.json();
return quotesArray;
});
requestQuotes('china') // 'china' 키워드에 대한 API 호출 결과 캐싱(Map의 'china' key에 저장)
requestQuotes.cache // Map(1) { 'china' -> ...} 대충 이런 모양
Memoize 메서드의 캐싱을 어떻게 구현하고 어디에 저장되는지 궁금해서 소스코드를 찾아봤다. 결론적으로 Map과 클로저를 활용해서 구현했다. memoize
함수의 첫번째 콜백이 받는 파라미터는 Map(캐시)의 key
로 사용된다. Map은 문자, 숫자 같은 원시형은 물론 참조형(함수, 객체 등)도 key
로 사용할 수 있기 때문에 Map을 활용하는 것 같다.
function memoize(func, resolver) {
if (
typeof func !== 'function' ||
(resolver != null && typeof resolver !== 'function')
) {
throw new TypeError('Expected a function');
}
const memoized = function (...args) {
const key = resolver ? resolver.apply(this, args) : args[0];
const { cache } = memoized;
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new (memoize.Cache || Map)();
return memoized;
}
memoize.Cache = Map;
참고로 memoize.Cache
프로퍼티는 Map 생성자 함수를 참조하도록 작성되어 있다. 이유는 잘 모르겠다.
memoize.Cache === Map // true
자바스크립트에서 함수는 일급 객체이므로, 프로퍼티를 추가/삭제 할 수 있다. 함수는 기본적으로 length
(파라미터 갯수)와 prototype
(생성자 함수일 때) 프로퍼티를 가진다. 익명함수가 아니면 name
프로퍼티도 가진다.
const add10 = (num) => num + 10;
Object.getOwnPropertyDescriptors(add10)
// length: {value: 1, writable: false, enumerable: false, configurable: true}
// name: {value: 'add10', writable: false, enumerable: false, configurable: true}
add10.desc = '숫자를 인자로 받아 10을 더한 값을 반환하는 함수'
console.log(add10.desc) // '숫자를 인자로 받아 10을 더한 값을 반환하는 함수'
delete add10.desc // true
console.log(add10.desc) // undefined
<aside>
💡 memoize()
파라미터에 resolver 함수를 넘기면, resolver가 반환하는 값을 캐시의 key로 사용한다.
</aside>
숫자를 인자로 받아 10을 더한 값을 반환하는 add10
함수를 memoize
의 첫번째 파라미터로 넘긴다고 가정해본다. memoize
함수가 반환하는 함수는 cache
(Map 객체)라는 프로퍼티를 가지며, 여기에 캐싱될 값들이 담긴다. 파라미터로 넘겼던 add10
함수는 memoize
함수의 지역 변수로 등록돼서 사용된다(클로저).
const add10 = (num) => num + 10;
const cachedValue = memoize(add10);
console.log(cachedValue) // ƒ (...args) {...}
console.log(cached.cache) // Map(0) {size: 0}
cachedValue
파라미터에 8
을 넘겨 실행했다고 가정하면...
cachedValue(8)
resolver
함수가 없으므로 파라미터로 받은 8
이 key
가 된다(args는 [8]
)cache
(Map) 객체에 8
이라는 key가 있는지 확인