FrontEnd/인간 JS 엔진되기

2. promise ,async await , closure

Tony Lim 2022. 6. 4. 15:03

Promise

즉시 return 되고 결괏값을 나중에 쓸 수 있다.

function cal(callback,a,b) {
    return callback(a,b)
}

cal((x,y) => x+y , 1,2)
3

비동기 + 콜백 이다. 콜백은 항상 비동기가 아니다.

 

const callback = () => console.log(1)
setTimeout(callback, 1000);
const p = new Promise((resolve,reject) => {
    resolve(1)
})


p.then((result) => {
    console.log(result)
})

setimeout처럼 결속되어있지 않고 나중에 결과 값을 쓸 수 있다.

 

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// expected output:
// "fulfilled"
// "rejected"

Promise.all 보다는 Promise.allSettled 를 써야한다. all의 경우 여러 promise들 중 하나라도 실패(reject)되면 다 실패한다.

allSettled 는 result 들중 실패 성공을 다 나열해준다. all 의 경우 다 catch로 가는데 몇번째 promise가 틀렸는지 알길이 없다.


 

비동기는 동시의 문제가 아니라 순서의 문제

 

setTimeout(() => {
    console.log(1)
},0)
setTimeout(() => {
    console.log(2)
},0)

setTimeout이 callstack 에 올라가자마 즉시 return 이되고 webapi setTimeout에게 위임된다.

webAPI 부분에서는 여러 workthreads 들이 존재하여 병렬로 해결할 수 있다. 다 되면 Task Queue에 결과값을 쌓는다.

event loop 주기마다 call stack에 anonymous 까지 다 끝났으면 Task Queue에서 하나씩 올라간다. () => console.log(1) 이것이 올라가서 실행됨

event loop 는 microtask queue 를 항상 먼저 살펴본다. 그래서 Promise 가 더 빠르게 call stack에 올라간다. 여기가 계속 안비면 TaskQueue(macro queue) 는 실행이 계속 지연됨

 


 

let a = 2;
const p = new Promise((resolve,reject) => {
    console.log('제일 먼저')
    setTimeout(() => {
        a = 5;
        console.log(a)
        resolve(a)
    },0)
})

console.log("딴짓")

p.then((result) => {
    console.log('result', result)
})

제일 먼저
딴짓
5
result 5

Promise의 동기부분이 존재한다. (resolve,reject) => {} 안의 부분은 다 동기이다.

Promise 가 주어진 익명함수를 호출한다. 

 


promise -> async

async 는 오른쪽에서 왼쪽 , Promise는 왼쪽에서 오른쪽

async function a() {
    const a = await 1;
    console.log('a',a)
    console.log('hmm')
    await null
    const b = await Promise.resolve(1)
    console.log('b',b)
    return b
}

Promise.resolve(1)
    .then((a) => {
        console.log(a)
        console.log('hmm')
        return null
    })
    .then(() => {
        return Promise.resolve(1)
    })
    .then((b) => {
        console.log('b',b)
        return b;
    })

하지만 promise ,async 는 1대1 대응이 아니다.

3번째 then에서는 1번쨰 then 인자로 들어온 a를 참조할 수 없다. 계속해서 then 의 인자값으로 넘겨주어야 가져다 쓸 수 있다.

await 1 도 비동기로 돌아간다. await 뒤가 동기라도 비동기로 돌아감

async function a() {
    console.log("returns here")
    const a = await 1;
    console.log('a',a)
    console.log('hmm')
    await null
    const b = await Promise.resolve(1)
    console.log('b',b)
    return b
}
a()
VM2797:2 returns here
VM2797:4 a 1
VM2797:5 hmm
VM2797:8 b 1
Promise {<fulfilled>: 1}

await 바로 전까지 동기로 호출 된다. 여기까지 호출되고 바로 call stack에서 빠져나오게 된다.

background로 들어가게 된 await, then 들은 promise가 resolve,reject 될때까지 대기하다가 queue에 들어가고 callstack이 비어있으면 event loop 때 올라가게 된다.

 

function delayP(ms) {
    return new Promise((resolve,reject) => {
        setTimeout(resolve,ms)
    })
}
async function b() {
    await delayP(3000)
    await delayP(6000)
    await delayP(9000)
}
async function b() {
    const p1 = delayP(3000)
    const p2 = delayP(6000)
    await Promise.allSettled([p1,p2])
    await delayP(9000)
}

1번째 b 함수는 총 3+6+9 = 18 초걸리지만

2 번째 함수 처럼 promise allsettled 를 통해 의도한대로 p2가 끝날떄까지 기다리는것을 최적화 할 수 있다. 6+9 =15

p1,p2가 서로 연관성이 없어서 뒷단에서 동시에 진행되도 무방한 경우 유용하게 사용가능하다.

 


 

Closure (함수와 함수 외부와의 관계)

function a() {
    for (var i = 0; i<5; i++) {
        setTimeout(() => {
            console.log(i)
        },i*1000)
    }
}
a();

실행하면 5가 5번나오게 된다.

var의 경우 function scope를 따른다. 이때 반복문이 5번 돌면서 setTimeout이 차례대로 background로 타이머를 0,1000,2000.. 가지면서 5개가 쌓이게 된다.

이때 i = 5가 되면서 반복문을 종료하며 a도 종료되며 call stack이 텅 비게된다.

그러면 background에서 webapi 가 작업을 끝내고 Macro Queue 에 올리고 callstack이 비어있으니 console.log 함수가 callstack에 올려진다.

이떄 console.log는 i를 참조하려 한다. i는 이미 5가 되게 된다.

 

let으로 바꾸면 for loop의 block scope가 적용된다. i 가 a scope 에 속하지 않고 5번 생기는 for loop의 scope에 속하게 된다.

제대로 매 scope마다 1씩 증가된 값들을 참조가 가능하다.

 

function a() {
  for (var i = 0; i < 5; i++) {
    (function (j) {
      setTimeout(() => {
        console.log(i);
      }, i * 1000);
    })(i);
  }
}
a();

클로저 관계를 변경하여 해결하는 방법이다.

이때는 anonymous function scope 5개를 생성함으로 해결이 되는것이다.