코드스피치 강의 ES6+ 6회차 정리

August 30, 2020

Sync vs Async

Sync

  1. 실행결과를 return '결과'로 반환
  2. 프레임이 적재된 순서대로 실행하는것

Async

  1. 콜백(전달된 함수)을 통해 리턴값을 반환
  2. 콜백함수의 위치에 따라 goto 처럼 이동하게 위/아래로 이동하기 떄문에 디버깅하기 어려움(코드가 날라다닌다)
  3. Sync 로직 플로우(위에서 아래 / 좌에서 우)가 익숙하기 때문에 이해하기 어렵다

Generator

Breaking Block

  1. 블록이 다 실행시키전(결과를 반환)까지 정지를 시킬 수 없다
  2. break를 통해 블록을 중지 시킬 수는 있지만, 우리가 원하는게 아니다
  3. Generator Suspend를 통해 Flow를 정지시킬 수 있다(Yield 전)
  4. next() 함수를 통해 CoRoution(정지된 상태(지역변수)로 이동)를 실현 ⇒ 다시 Flow 진행
const infinity = (function* () {
    let i = 0
    while (true) yield i++
})()

console.log(infinity.next())
console.log(infinity.next())

Blocking Evasion

  1. Time Slice Manual
    • 로직과 알고리즘이 함께 있어 어느 부분이 변경이 된다면 손이 많이 간다
    • 클로져로 인해(자유변수 사용) 로직을 이해하는 것이 힘들다.
// async nonBlock
const looper = (n, f, slice = 3) => {
    let limit = 0,
        i = 0 // 자유변수
    const runner = (_) => {
        while (i < n) {
            if (limit++ < slice) f(i++)
            else {
                limit = 0
                requestAnimationFrame(runner)
                break
            }
        }
    }
    requestAnimationFrame(runner)
}

looper(10, console.log)
  1. Time Slice Manual Using Generator

    • Coroutine으로 인해 상태를 유지하기 때문에 지역변수로 사용하여 이해가 쉽다.
    • Flow Control과 Logic을 분리하여 유지보수에 용이하다(관심사 분리)
//데이터 로직
const loop = function* (n, f, slice = 3) {
    let limit = 0,
        i = 0 // 지역변수 (제네레이터의 지역변수)
    while (i < n) {
        if (limit++ < slice) f(i++)
        else {
            limit = 0
            yield
        }
    }
}
//실행기
const executor = (iter) => {
    const runner = (_) => {
        iter.next()
        requestAnimationFrame(runner)
    }
    requestAnimationFrame(runner)
}

executor(loop(10, console.log))
  1. Why use Generator

    • 제어 구조를 분리해서 재활용 할 수 있다(같이 있으면 재사용 불가)
    • Generator Block Braking 속성을 이용하면
      1. 실행기와 핵심로직을 분리
      2. 자유변수보다는 지역변수를 이용하여 로직을 단순화

Generator + Async + Executor(컨슈머 용법 활용)

  1. 데이터 로직

    const profile = function*(end, next, r){
        const userid = yield $.post('member.php', {r}, next);
        let added = yield $.post('detail.php', {userid}, next);
        added = added.split(",");
        end({userid, nick:added[0], thumb:added[1]});
    • profile 함수가 호출이 되면 yield까지 실행이 된다($.post()를 호출하고 나서 결과값을 가진 상태)
    • 하지만 userid로 할당이 되지 않는다(next()를 호출하기 전까지)
  2. 실행기

    const executor = (end, gene, ...arg) => {
        const next = (v) => iter.next(v)
        const iter = gene(end, next, ...arg)
        iter.next()
    }
    
    executor(console.log, profile, 123)
    • $.post가 결과값을 next로 넘겨주게 된다.
    • 값을 yield로 할당하면서 next()를 호출한다. 다음 yield까지 실행하게 된다.
  3. 좋은점?

    • 콜백지옥에서 탈출
    • 모든게 다 지역변수로 관리된다
    • 모든게 다 동기로직으로 변경되었다.

Promise

Passive Async Controll(수동적)

  1. 콜백을 보낼 수는 있지만 언제 올지는 모른다.

  2. 병렬로 진행될 수 있음에도 불구하고 순서 때문에 한번에 하나 씩 진행

    • useid를 받아와야지 프로필 정보를 받아올 수 있다.
    // 언제 결과가 올지 몰라 에러가 발생할 수도 있다
    let result
    
    $.post(url1, data1, (v) => {
        result = v
    })
    
    $.post(url2, data2, (v) => {
        result.nick = v.nick
        report(result)
    })
  3. 순서가 있는 콜백은 어쩔 수 없다. 현재 이 방법 뿐이다

Active Async Controll(능동적)

  1. 호출하기 전까지는 발동하지 않는다(Promise)
  2. Promise는 then을 호출해야지만 결과를 얻는다
  3. 요청이 완료되지 않으면 결과를 못받는다.
  4. 중요한 점은 then을 호출하기 전까지는 상태가 완료되도 실행되지 않는다.

Why Promise

  1. 실제적인 비동기 행위와 콜백의 처리를 완전히 분리 할수 있다.
    • 프로미스가 어떤 작업의 상태인지 우리 관심에서 멀어졌다.(then만 기억)
  2. 병렬/병행성 작업을 진행하여 순서가 없는 작업에 대해서 처리 할 수 있다.
    • promise1, promise2는 동시에 요청이된다. 그래서 all,race, some이 나온것임
  3. But!! 순서 잇는 로직은 적용하지 못하고 callback으로 진행, 처음부터 구조를 잘 짜야한다.
  4. 우리는 흐름제어만 신경쓰자!!
let result;
const promise = new Promise(r => $.post(url1, data1, r));
promise.then(v => {
	return = v;
})

const promise1 = new Promise(r => $.post(url1, data1, r));
const promise2 = new Promise(r => $.post(url2, data2, r));
promise1.then(result => {
	promise2.then(v => {
		result.nick = v.nick;
		report(result);
	})
});

Generator + Promise

  1. Promise 또한 비동기적인 작업 일 수 밖에 없다
  2. next()가 사라지면서 상태에 대한 관심이 분리되어 자기 참조 무결성이 더욱더 강해졌다.
  3. 초반에 생성을 할 수 있는 데이터를 일체 받고(병렬) 나중에 조립하는 로직만 프로미스로 하자
const profile = function*(end, r){
	const userid = yield new Promise(res => $.post('member.php', {r}, res));
	let added = yield new Promise(res => $.post('detail.php', {userid}, res));
	added = added.split(',');
	end({userid, nick:added[0], thunb:added[1]});

const executor = (end, gene, ...arg) => {
	const iter = gene(end, ...arg);
	const next = ({value, done}) => {
		if(!done) value.then(v => next(iter.next(v));
	};
	next(iter.next());
};

executor(console.log, profile, 123)

Async Await

  1. 제네레이터의 실행기가 언어적으로 내장되어 있어서 사용된다
  2. 하지만 동기적으로 작동하기 때문에 병렬안되나, await promise.all(()로 진행할 수 있다.
  3. AWAIT PROMISE = SYNC
  4. 코드가 어떻게 줄어졌는지? 어떤게 내장되어 있는지? 고민해보자.
const profile = async function(end, r){
	const userid = await new Promise(res => $.post('member.php', {r}, res));
	let added = await new Promise(res => $.post('detail.php', {userid}, res));
	added = added.split(',');
	end({userid, nick:added[0], thunb:added[1]}
);

profile(console.log, 123)

추가사항

  • 자바스크립트 함수는 내부의 문법 오류가 없다면 모든 변수를 실행 시점에 평가한다.
  • const / let을 사용하면 tdz를 생성해서 호이스팅을 방지한다.
  • 콜백에 대해서 순서없는 프로그래밍을 짜라 리엑티브 프로그래밍

© 2023, Customized by Joon