주기적으로 실행되는 애니메이션을 window.requestAnimationFrame()으로 최적화 해보자
requestAnimationFrame(callback)
브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트 하는 함수를 호출하게 한다. 이 메소드는 리페인트 이전에 실행할 함수를 인자로 받는다.
다음 리페인트에서 그 다음 프레임을 애니메이트 하려면 콜백 루틴이 반드시 스스로 requestAnimationFrame()을 호출해야 한다.
대부분의 브라우저에서 W3C 권장사항에 따라 디스플레이 주사율만큼 호출되게 된다.
callback
파라미터인 callback은 다음 리페인트를 위한 애니메이션을 업데이트 할 때 호출할 함수이다. 콜백함수에는 requestAnimationFrame()이 콜백함수 실행을 시작할 때의 시점을 나타내는 DOMHighResTimeStamp() 단일 인자를 전달한다.
반환 값
requestAnimationFrame()을 취소할 수 있는 고유 id인 long 정수값이 반환된다. window.cancelAnimationFrame()함수로 전달하여 취소할 수 있다.
차이점
setInterval과는 다음과 같은 차이점이 있다.
주사율만큼의 interval
setInterval을 사용하여 구현할 경우 interval을 손수 설정해주어야 한다. requestAnimationFrame은 주사율만큼의 Interval을 가지게 된다.(설정해줄 필요가 없다.)
동시 실행
여러개의 setInterval을 사용할 경우 콜백이 겹쳐져서 버벅임이 발생할 수 있다. requestAnimationFrame을 사용할 경우 계속 실행하기 위해선 내부 callback에서 반드시 재호출해야 하므로 여러개의 애니메이션을 써도 버벅이지 않는다.
비동기
위의 동시실행과 같은 이야기지만 setTimeout, setInterval은 마이크로 태스크큐에서 작동한다. 하지만 requestAnimationFrame은 Animation Frame에서 동작한다. 때문에 setInterval과 달리 callback이 유실될 가능성이 없다.
예시
1 2 3 4 5 6 7 8 9 10 11 12
let start = null; let hi = 0; functioncallback(timestamp) { if (!start) start = timestamp; console.log("cnt", hi++);
let times = [1000, 2000, 3000, 4000]; Promise.all(times.map((time) => timer(time))); console.log("complete");
// 1000 타이머 시작 // 2000 타이머 시작 // 3000 타이머 시작 // 4000 타이머 시작 // 1000 타이머 종료 // 2000 타이머 종료 // 3000 타이머 종료 // 4000 타이머 종료 // complete
forawait (let time of times) { timer(time); }
// 1000 타이머 시작 // 1000 타이머 종료 // 2000 타이머 시작 // 2000 타이머 종료 // 3000 타이머 시작 // 3000 타이머 종료 // 4000 타이머 시작 // 4000 타이머 종료 // complete
즉 for await of는 Promise간의 실행순서를 보장하지만 Promise.all은 그러지 못한다.
4. Promise.allSettled
Promise.allSettled는 Promise.all과 같은 기능을 하지만 Promise.all은 부분적으로 실패할 경우에 배열 전체의 실행이 중지되고 catch문으로 이동한다. 하지만 allSettled는 배열의 모든 Promise를 실행하고 상태와 결과값을 하나의 객체로 resolve문에 넘긴다.
Promise.race([promise1, promise2]).then((value) => { console.log(value); // two });
정리
순서가 보장되어야 하는가? => for await of 일부 Promise에서 에러가 발생할 경우 전체의 실행을 멈추고 싶은가? => Promise.all 일부 Promise에서 에러가 발생하더라도 에러가 발생한 일부 Promise만 따로 예외처리 해주고 싶은가? => Promise.allSettled 가장 먼저 실행이 된 Promise의 결과만 다루고 싶은가? => Promise.race
const reduce = (f, acc, iter) => { if (!iter) { iter = acc[Symbol.iterator](); acc = iter.next().value; } // parameter가 2 개만 들어왔을 경우 iter가 없는 경우이다. // 이 때는 자기 자신을 반환하는 iterable의 성질을 이용해서 iter를 만들어준다. for (const a of iter) { acc = f(acc, a); } return acc; };
const map = (f, iter) => { let res = []; for (const p of iter) { res.push(f(p)); } return res; }; console.log(document.querySelectorAll("*").map((ele) => ele.nodeName)); // 배열이 아니기 때문에 에러가 발생한다. console.log(map((el) => el.nodeName, document.querySelectorAll("*"))); // nodeList는 이터러블 프로토콜을 따르기 때문에 동작한다.
function* oods(l) { for (let i = 0; i < l; i++) { if (i % 2) yield i; } } let iter2 = oods(6); console.log(iter2.next()); // {value:1,done:false} console.log(iter2.next()); // {value:3,done:false} console.log(iter2.next()); // {value:5,done:false} console.log(iter2.next()); // {value:undefined,done:true}
또한 generator는 while을 이용하여 작성할 수 있다.
아래의 infinity는 무한수열을 만드는 generator이고 limit 함수는 l까지는 a를 yield하다가 l이 될 경우 return 하는 generator이다. infinity generator와 limit generator를 이용하여 odds generator도 새로 정의하였다.
// 배열 순회 const list = [1, 2, 3]; for (var i = 0; i < list.length; i++) { console.log(list[i]); } // 유사 배열 순회 const str = "abc"; for (var i = 0; i < str.length; i++) { console.log(str[i]); }
// 1 // 2 // 3 // a // b // c
for of
ES6에선 for of문이 추가되어 좀 더 명령형에서 선언형으로 바뀌었다. 인덱스로 직접 접근할 수 없는 리스트도 순회할 수 있다.
IteratorResult 객체는 done: boolean과 value: any 프로퍼티를 - 가진다.
이전 next 메서드 호출의 결과로 done 값이 true를 리턴했다면, 이후 호출에 대한 done 값도 true여야 한다.
Well-formed iterable
Iterator면서 Iterable인 객체를 Well-formed iterable이라고 한다. 즉 이터레이터의 이터러블이 자기 자신을 반환하면 Well-formed iterable이다.
Iterable/Iterator 프로토콜
Iterable을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약이다.
for of의 동작원리
for of는 Iterable 객체에 사용할 수 있다.
for of를 호출하게 되면 Symbol.iterator를 통해 Iterator를 반환한다.
즉 for of는 Iterable/Iterator 프로토콜에 따라 동작한다.
1 2 3 4 5 6 7
const list = [1, 2, 3]; for (const a of list) { // for of는 Iterable/Iterator 프로토콜에 따라 동작한다. // 즉 반환된 Iterator의 value를 a로 참조한다. // done이 true가 되면 반복문을 빠져나온다. console.log(a); }
따라서 코드를 다음과 같이 수정할 수 있다.
1 2 3 4 5 6 7
let list = [1, 2, 3]; let iterator = list[Symbol.iterator](); while (1) { const { value, done } = iterator.next(); if (done === true) break; console.log(value); }
const map = newMap([ ["a", 1], ["b", 2], ["c", 3], ]); for (const a of map.keys()) console.log(a); // Map,Set의 key를 value로 하는 Iterator를 반환하는 메서드이다. // a // b // c for (const a of map.values()) console.log(a); // Map,Set의 value를 value로 하는 Iterator를 반환하는 메서드이다. // 1 // 2 // 3 for (const a of map.entries()) console.log(a); // Map,Set 전체를 value로 하는 Iterator를 반환하는 메서드이다. // ['a',1] // ['1',2] // ['1',3]
const a=[1,2=]; console.log([...a,...[3,4]]) ///[1,2,3,4]; a[Symbol.iterator]=null; console.log([...a,...[3,4]]) // Uncaught TypeError: a is not iterable
Iterable 프로토콜을 따르고 있기 때문에 Map,Set과 같은 여러 Iterable객체에 적용된다.
return ( <divonClick={onClick()}></div> // 즉시 실행되어서 e is undefined 에러가 발생한다. <divonClick={onClick}></div> // <div onClick={(e)=>{console.log(e.target)}></div> } ) }