본문 바로가기

개발/JavaScript

코드 동작원리 예제

728x90
반응형

🔵 사용할 코드

router.post("/latestUser", async (req, res) => {
  const Op = Sequelize.Op;
  const tradeList = await User.findAll({
    order: [["createdAt", "DESC"]],
  });
  console.log("tradeList", tradeList);

  const tempTrade = [];
  const tempUser = [];
  for (let i = 0; i < tradeList.length; i++) {
    if (!tempTrade.includes(tradeList[i].sellerAddress)) {
      if (tempTrade.length < 3) {
        tempTrade.push(tradeList[i].userAddress);
        tempUser.push(
          await User.findOne({
            where: { userAddress: tradeList[i].userAddress },
          })
        );
      } else {
        break;
      }
    }
  }

  console.log("tempUser", tempUser);
  const tempNft = [];
  for (let i = 0; i < tempTrade.length; i++) {
    const list = await Nft.findAll({
      limit: 3,
      where: { userAddress: tempTrade[i], state: "selling" },
      order: [["createdAt", "DESC"]],
    });
    tempNft.push(list);
  }
  console.log("tempNft", tempNft);

  res.send({ userArr: tempUser, nftArr: tempNft });

db에 최근에 등록된 순서대로 user 목록을 뽑고, 최대 3명까지의 정보를 list에 중복없이 담고, 3명의 user가 각각 판매하는 가장 최근 등록된 상품을 최대 3개까지 뽑아내는 router 코드이다.

선정 이유 : await 와, 적당한 배열, for문까지 javascript 동작원리를 직접 그려나가기 적합하다고 생각했기 때문이다.

동작 순서 :
0. router 함수 전체를 감싸는 익명함수가 콜스택 맨 하위에 들어간다.
1. 메모리에 Op 주소 지정(undefined)
2. Sequeliuze.op 이 콜스택에 들어가 Op가 가리키는 장소에 할당 후 pop
3. 메모리에 tradeList 주소 지정(undefined)
4. tradeList 를 초기화하려고 보니 await 할당문이다. 따라서 callback queue로 이동
5. ☆ router 함수 전체를 감싸는 익명함수는 functional execution context인데, 이 context를 제외하고는 실행할 코드가 없으므로 callstack이 비게된다.
6. 콜백큐에서 바로 전체 함수가 콜스택으로 옮겨지며, 바로 await로 중지시켰던 tradeList 가 가리키는 장소에 할당후 pop
7. console.log( tradelist ) 가 callstack에 들어오고, 찍히면서 pop
8. tempTrade 주소 지정 (undefined)
9. tempUser 주소 지정 (undefined)
10. for문 call stack으로 이동
 10-1. tempTrade가 가리키는 정보에 tradeList[i] 의 sellerAddress가 없는지 확인 -> 있으면 i++ 후 다시 10번으로
 10-2. tempTrade가 참조하는 정보의 길이가 3보다 작은지 확인 -> 길면 i++ 후 다시 10번으로
 10-3. tempTrade.push가 callstack으로 이동해 실행되고 pop
 10-4. tempUser.push가 callstack으로 이동해 실행되었지만, push() 속 인자가 await 할당문이므로 pop되지 않고 대기
 10-5. 5번과 같은 원리로 콜백큐로 갔다가 다시 콜스택으로 돌아오면서 push() 속 인자가 초기화되고, push 메서드 실행후 tempUser.push가 callstack에서 pop된다.
 10-1~ 10-5를 반복하다 for문 순회가 끝나고 for문이 callstack에서 pop
11. console.log(tempUser) 가 callstack에 들어오고 console 이 찍히며 pop
12. tempNft 주소 지정 (undefined)
13. for문 call 스택으로 이동
 13-1. const list 주소 지정(undefined) 후 5번과 같은 원리로 이동하여 돌아왔다가 list 속 장소에 할당완료 후 pop
 13-2. tempNft.push 가 call스택으로 호출된 후 push 완료와 함께 pop
 13-1~13-2 를 반복하다가 for문 조건을 벗어나면 for문이 콜스택에서 빠져나간다.
14. tempNft 콜스택에 들어와 찍히고, pop
15. res.send method를 호출하여 콜스택에 들어와 data를 front에 전송하고 pop.

 

 

🟠 synchronous, asynchronous

- 동기 : 직렬적으로 태스크를 수행하는 방식, 요청을 보낸 후 응답을 받아야지만 다음 동작이 이루어지는 방식.
하나의 일을 하는 동안 나머지 일들은 대기하게 된다.
- 비동기 : 병렬적으로 태스트를 수행하는 방식, 요청을 보낸 후 응답의 수락 여부와는 상관없이 다음 태스크가 동작하는 방식. 하나의 일을 하는 동안 다른 일을 할 수 있다. 응답후 처리할 콜백함수도 함께 알려주고, 해당 태스트가 완료 되었을 때 콜백 함수가 호출된다.

callback 함수 : 다른 코드의 인수로서 넘겨주는 실행 가능한 코드. 필요에 따라 즉시실행을 할 수도, 나중에 실행을 할 수도 있다.

 

🟢 사전 조사해야할 사항

  • async/await : 대충 db탐색할때, router 통신할 때 쓰는 정도라고만 알고 넘어가버려서 정확히 어떤 녀석인지, 동기인지 비동기인지도 모르기에 그려나갈 때 방해가 된다.
  • for문 : 간단한 연산 반복문은 동기적일 것 같은데 그 안에 await 가 끼게 되면 어떻게 처리될지 잘 모르겠다.
  • db 관련문 : async/await 와 같은 맥락일 것 같긴한데, db와 접촉하여 어떠한 작업을 수행하긴 하니까 콜스택에서 어떻게 처리되는지 콜스택에서 처리하긴 하는건지 잘 모르겠다.

🟣 사전 조사사항 공부

  • async/await : JS 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 promise의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.
    - async 키워드는 function 앞에 사용하는데, 해당 함수는 항상 promise를 반환하게 된다. promise가 아닌 값을 반환하더라도 resolved promise로 감싸 이행된 promise가 반환되도록 한다. promise 는 아래에 따로 chapter로 정리해야겠다.
// 일반함수
async function 함수명() {
	await 비동기method명();
}

// 화살표함수
const 함수명 = async () => {
	await 비동기method명();
}
----

// promise로 감싸 반환
async function f() {
  return 1;
}

f().then(alert); // 1

// promise로 반환
async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

- await : async 함수 안에서만 동작한다. promise가 처리될 때 까지 기다리는 역할을 하고, 결과는 이후 반환된다.
그러나 단순히 await의 결과를 기다리며 코드 동작을 일시정지 한다고 생각하면 안된다.

## js engine이 await를 만나면 일어나는 일
1. await 키워드가 붙은 대상이 함수라면 해당 함수를 실행
2. 함수가 아니거나/ 함수의실행이 끝나면 해당 async 함수를 일시정지하고 콜스택에서 microtask queue로 옮긴다.
3. await의 위치를 기억한다.
4. 해당 함수가 콜스택에서 빠져 나왔으니 나머지 콜스택이 실행된다.
5. 콜스택이 모두 비워지면 이벤트 루프는 microtask queue 에 있는 await 키워드를 만나 옮겨진 async 함수를 다시 콜스택 으로 옮긴다.
6. 해당함수가 await 됐던 시점부터 다시 실행된다.
- key point : 해당 함수가 일시정지된 상태에서 콜스택에서 빠져나와 microtask queue 로 옮겨진다.
즉 await는 자신이 포함된 async 함수만을 일시정지 시킨다는 것이다.

코드 예시

const a = () => {
  console.log("a 시작");
  b();
  console.log("a 끝");
};

const b = async () => {
  console.log("b 시작");
  await c();
  console.log("b 끝");
};

const c = async () => {
  console.log("c 시작");
  await d();
  console.log("c 끝");
};

const d = () => {
  console.log("d")
};

a();

결과는 아래와 같다.

a 시작
b 시작
c 시작
d
a 끝
c 끝
b 끝

실행 동작 순서는 아래와 같다.

1. a함수가 호출되어 콜스택에 쌓이고, a 시작이 콘솔에 찍힌다.
2. b함수가 호출되어 콜스택에 쌓이고 b시작이 콘솔에 찍힌다.
3. await를 만났지만 즉시 멈추는게 아니라, c함수를 호출하여 콜스택에 올린다.
4. 3번으로 인해 c 시작이 콘솔에 찍힌다.
5. await를 만났지만 d함수를 호출하여 콜스택에 올린다.
6. 5번으로 인해 d가 콘솔에 찍힌다.
7. c함수에서 d를 호출했던 await로 인해 c함수가 일시정지되며 콜스택을 빠져나가 microtask queue 에 들어간다.
8. 콜스택에서 c함수 자체가 빠져나갔기에 b함수가 마저 실행됨과 동시에 b함수도 7번과 같이 microtask queue 에 들어간다.
9. 콜스택에서 b함수가 빠져나갔기에 a함수가 마저 실행되며 a끝이 콘솔에 찍힌다.
10. 콜스택이 모두 비워졌기에 이벤트 루프가 micro task queue의 첫 번째 작업인 c함수를 콜스택으로 옮긴다.
11. c함수가 일시정지된 await 이후부터 실행되며, 콘솔에 C끝이 찍히고 콜스택이 비워진다.
12. 콜스택이 모두 비워졌기에 이벤트 루프가 micro task queue의 첫 번째 작업인 b함수를 콜스택으로 옮긴다.
13. b함수가 일시정지된 await 이후부터 실행되며, 콘솔에 C끝이 찍히고 콜스택이 비워진다.

- async / await 가 비동기 코드를 동기코드처럼 보여주는 원리는 해당 함수를 micro task queue 에 옮겨놓고 콜스택이 비워졌을 때 실행하기 때문이다.

🟡 promise 

- 단순 정의 : 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
 - pending(대기) : 이행하지도, 거부하지도 않은 초기 상태
 - fulfilled (이행) : 연산이 성공적으로 완료된 상태.
 - rejected (거부) : 연산이 실패한 상태.

// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
  // 비동기 작업을 수행한다.

  if (/* 비동기 작업 수행 성공 */) {
    resolve('result');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure reason');
  }
});

- promise 가 보류상태에 있으면 해당 작업이 아직 완료되지 않았음을 의미하고, 작업이 성공적으로 완료되면 약속이 이행되고 값을 반환한다. 오류가 발생하면 promise가 거부되고, 오류 객체를 반환한다.

- API 에서 데이터를 가져오거나 파일에서 읽는 것과 같은 비동기 작업을 처리하는 데 사용한다.

- 함수를 인수로 취하는 생성자를 사용하여 생성된다. executor 함수라고 하는 이 함수는 resolve와 reject 두 개의 인수를 사용하는데, resolve 는 작업이 성공했을 때 호출되는 함수이고, reject 는 실패했을때 호출이 되는 함수이다.
resolve 던 reject 던 결과가 발생하면 후속처리 메소드로 결과를 전송한다.

- 후속 처리 메소드로는 then()과 catch() 라는 두 가지 기본 메서드가 있다.
 - then() : promise가 정상적으로 이행되었을 때의 콜백함수와 거부되었을 때의 콜백함수 두 개의 인수를 사용하고, promise를 반환한다.
 - catch() : 비동기 처리에서 발생한 error 와 then 메소드에서 발생한 error 가 발생하면 호출되며, promise를 반환한다.

## primise chainning
- 비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 콜백 헬이 발생한다. 따라서 후속처리 메서드(then, catch)로 메소드를 chainning 하여 여러 개의 프로미스를 연결하여 사용한다. -> 콜백 헬 해결.
- then 메소드가 promise 객체를 반환하도록 하면 여러 개의 promise를 연결하여 사용할 수 있다는 뜻

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

?? 후속 처리메소드인데 다시 promise를 반환한다?

- 요약

 

🟡 Reference

https://velog.io/@khy226/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%9E%80-Promise-asyncawait-%EA%B0%9C%EB%85%90 >> 동기 vs 비동기
https://www.youtube.com/watch?v=-iZlNnTGotk callback 함수
https://ko.javascript.info/promise-chaining Promise chainning
https://velog.io/@jjunyjjuny/JavaScript-asyncawait%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C >>  await 콜스택

 

 

728x90
반응형

'개발 > JavaScript' 카테고리의 다른 글

FormData  (0) 2023.04.19
Javascript 기초  (0) 2023.04.04


Calendar
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Visits
Today
Yesterday