본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 7: Promise와 async/await ꡬ문

πŸ¦„ 비동기 콜백 ν•¨μˆ˜β€‹

πŸ“š 동기와 비동기 API​

  • Node.jsμ—μ„œ 파일 μ‹œμŠ€ν…œκ³Ό κ΄€λ ¨λœ κΈ°λŠ₯을 λͺ¨μ•„λ‘” fs νŒ¨ν‚€μ§€λ₯Ό μ œκ³΅ν•˜λŠ”λ°, 동기 비동기 λ²„μ „μœΌλ‘œ λ‚˜λˆ„μ–΄ μ œκ³΅ν•œλ‹€. 예λ₯Ό λ“€μ–΄, 동기 버전인 readFileSync와 비동기 버전인 readFile둜 μ œκ³΅ν•œλ‹€.
import { readFileSync, readFile } from "fs";

// 동기 λ°©μ‹μœΌλ‘œ 읽기
const buffer: Buffer = readFileSync('./package.json');
console.log(buffer.toString());

// 비동기 λ°©μ‹μœΌλ‘œ 읽기
readFile('./package.json', (error: Error, buffer: Buffer) => {
console.log(buffer.toString());
});

// Promise와 async/await ꡬ문을 μ‚¬μš©ν•œ 예
const readFilePromise = (filename: string): Promise<string> =>
new Promise<string>((resolve, reject) => {
readFile(filename, (error: Error, buffer: Buffer) => {
if(error) {
reject(error);
} else {
resolve(buffer.toString());
}
});
});

(async () => {
const content = await readFilePromise('./package.json');
console.log(content);
})();
  • API ν•¨μˆ˜λŠ” 일반 ν•¨μˆ˜μ™€ 달리 ν•˜λ“œλ””μŠ€ν¬μ— μ €μž₯된 νŒŒμΌμ„ μ½λŠ” λ“± μ‹€ν–‰μ‹œ 물리적인 μ‹œκ°„μ΄ μ†Œμš”λœλ‹€.
  • λ”°λΌμ„œ 파일 λ‚΄μš©μ„ λͺ¨λ‘ 읽을 λ•ŒκΉŒμ§€ ν”„λ‘œκ·Έλž¨μ˜ λ™μž‘μ„ μž μ‹œ λ©ˆμΆ”λŠ” 동기 λ°©μ‹μ˜ API와 ν”„λ‘œκ·Έλž¨μ˜ λ™μž‘μ„ λ©ˆμΆ”μ§€ μ•ŠλŠ” λŒ€μ‹  κ²°κ³Όλ₯Ό 콜백 ν•¨μˆ˜λ‘œ μ–»κ²Œ ν•˜λŠ” 비동기 λ°©μ‹μ˜ APIλ₯Ό μ œκ³΅ν•œλ‹€.
  • 비동기 API의 콜백 ν•¨μˆ˜λ₯Ό 비동기 콜백 ν•¨μˆ˜λΌκ³  ν•œλ‹€. 비동기 콜백 ν•¨μˆ˜λŠ” 일반 ν•¨μˆ˜μ™€ 달리 API의 물리적인 λ™μž‘ κ²°κ³Όλ₯Ό μˆ˜μ‹ ν•˜λŠ” λͺ©μ μœΌλ‘œλ§Œ μ‚¬μš©ν•œλ‹€.

πŸ“š 단일 μŠ€λ ˆλ“œμ™€ 비동기 API​

  • μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 단일 μŠ€λ ˆλ“œλ‘œ λ™μž‘ν•˜λ―€λ‘œ 될 수 있으면 readFileSync와 같은 동기 APIλ₯Ό μ‚¬μš©ν•˜μ§€ 말아야 ν•œλ‹€.
  • 동기 APIκ°€ μ‹€ν–‰λ˜λ©΄, μš΄μ˜μ²΄μ œλŠ” 동기 API의 μž‘μ—… κ²°κ³Όλ₯Ό ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μœΌλ‘œ λŒλ €μ€˜μ•Ό ν•œλ‹€. 이 λ•Œλ¬Έμ— μš΄μ˜μ²΄μ œλŠ” 동기 APIκ°€ μ‹€ν–‰λœ μ½”λ“œλ₯Ό μΌμ‹œμ μœΌλ‘œ 멈좘 λ‹€μŒ, 또 λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ μ‹€μ œ μž‘μ—…μ„ μ‹€ν–‰ν•΄ μΌ€κ³Όλ₯Ό μ–»μœΌλ©΄ κ·Έλ•Œμ„œμ•Ό μž μ‹œ λ©ˆμ·„λ˜ 동기 APIλ₯Ό μ‹€ν–‰ν•˜λ©΄μ„œ 결괏값을 λ°˜ν™˜ν•œλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— κ²°κ³Όλ₯Ό λ°˜ν™˜ν•  λ•ŒκΉŒμ§€ μΌμ‹œμ μœΌλ‘œ λ©ˆμΆ”λŠ” ν˜„μƒμ΄ λ°œμƒν•œλ‹€.

πŸ“š 콜백 지μ˜₯​

  • 비동기 APIλ₯Ό μ‚¬μš©ν•˜λ©΄ 콜백 ν•¨μˆ˜μ—μ„œ 또 λ‹€μ‹œ λ‹€λ₯Έ 비동기 APIλ₯Ό ν˜ΈμΆœν•˜λŠ” μ½”λ“œλ₯Ό λ§Œλ“€ λ•Œ μ½”λ“œκ°€ 맀우 λ³΅μž‘ν•΄μ§„λ‹€.
import { readFile } from "fs";

readFile('./package.json', (error: Error, buffer: Buffer) => {
if (error) {
throw error;
} else {
const content: string = buffer.toString();
console.log(content);
}

readFile('./tsconfig.json',(err: Error, buffer: Buffer) => {
if (error) {
throw error;
} else {
const content: string = buffer.toString();
console.log(content);
}
})
});
  • Promiseλ₯Ό μ‚¬μš©ν•˜λ©΄ 이런 콜백 지μ˜₯에 빠진 μ½”λ“œλ₯Ό μ’€ 더 닀루기 μ‰¬μš΄ ν˜•νƒœμ˜ μ½”λ“œλ‘œ λ§Œλ“€ 수 μžˆλ‹€.

πŸ¦„ Promise μ΄ν•΄ν•˜κΈ°β€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ PromiseλŠ” λ‹€μŒκ³Ό 같이 μ œλ„€λ¦­ 클래슀 ν˜•νƒœλ‘œ μ‚¬μš©λœλ‹€.
const numPromise: Promise<number> = new Promise<number>(μ½œλ°±ν•¨μˆ˜);
const strPromise: Promise<string> = new Promise<string>(μ½œλ°±ν•¨μˆ˜);
const arrayPromise: Promise<number[]> = new Promise<number[]>(콜벑힘수);
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ Promise의 콜백 ν•¨μˆ˜λŠ” λ‹€μŒμ²˜λŸΌ resolve와 reject ν•¨μˆ˜λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›λŠ” ν˜•νƒœμ΄λ‹€.
new Promise<T>((
resolve: (successValue: T) => void,
reject: (any) => void,
) => {
// μ½”λ“œ κ΅¬ν˜„
});

πŸ“š resolve와 reject ν•¨μˆ˜β€‹

  • λ‹€μŒμ€ 비동기 API인 readFile을 ν˜ΈμΆœν•˜λŠ” λ‚΄μš©μ„ ν”„λ‘œλ―ΈμŠ€λ‘œ κ΅¬ν˜„ν•œ μ˜ˆμ΄λ‹€.
import { readFile } from 'fs';

export const readFilePromise = (filename: string): Promise<string> =>
new Promise<string>((
resolve: (value: string) => void,
reject: (error: Error) => void) => {
readFile(filename, (err: Error, buffer: Buffer) => {
if(err) {
reject(err);
} else {
resolve(buffer.toString());
}
})
}
)
  • λ‹€μŒ μ½”λ“œλŠ” readFilePromise ν•¨μˆ˜κ°€ λ°˜ν™˜ν•˜λŠ” Promise νƒ€μž… 객체의 then, catch, finally λ©”μ„œλ“œλ₯Ό λ©”μ„œλ“œ 체인 ν˜•νƒœλ‘œ μ‚¬μš©ν•œλ‹€.
import { readFilePromise } from "./readFilePromise";

readFilePromise('./package.json')
.then((content: string) => {
console.log(content);
return readFilePromise('./tsconfig.json');
})
.then((content: string) => {
console.log(content);
return readFilePromise('.');
})
.catch((err: Error) => console.log('error: ', err.message))
.finally(() => console.log('ν”„λ‘œκ·Έλž¨ μ’…λ£Œ'));

πŸ“š Promise.resolve와 Promise.reject λ©”μ„œλ“œβ€‹

  • Promise.resolve(κ°’) ν˜•νƒœλ‘œ ν˜ΈμΆœν•˜λ©΄ 항상 이 갑은 then λ©”μ„œλ“œμ—μ„œ 얻을 수 μžˆλ‹€.
Promise.resolve({ name: 'Jack', age: 32 })
.then(value => console.log(value)); // { name: 'Jack', age: 32 }
  • Promise.reject(Error νƒ€μž… 객체)λ₯Ό ν˜ΈμΆœν•˜λ©΄ 이 Error νƒ€μž… κ°μ²΄λŠ” 항상 catch λ©”μ„œλ“œμ˜ 콜백 ν•¨μˆ˜μ—μ„œ 얻을 수 μžˆλ‹€.
Promise.reject(new Error('μ—λŸ¬ λ°œμƒ'))
.catch((err: Error) => console.log('error: ', err.message)); // error: μ—λŸ¬ λ°œμƒ

πŸ“š then-체인​

  • Promise 객체에 then λ©”μ„œλ“œλ₯Ό μ—¬λŸ¬ 번 ν˜ΈμΆœν•˜λŠ” μ½”λ“œ ν˜•νƒœλ₯Ό then-체인이라고 ν•œλ‹€.
Promise.resolve(1)
.then((value: number) => {
console.log(value); // 1
return Promise.resolve(true);
})
.then((value: boolean) => {
console.log(value); // true
return [1, 2, 3];
})
.then((value: number[]) => {
console.log(value); // [1, 2, 3]
return { name: 'jack', age: 32 };
})
.then((value: { name: string, age: number }) => {
console.log(value); // { name: 'jack', age: 32 }
})

πŸ“šPromise.all λ©”μ„œλ“œβ€‹

  • Promise.all λ©”μ„œλ“œλŠ” Promise 객체λ₯Ό λ°°μ—΄ ν˜•νƒœλ‘œ λ°›μ•„, λͺ¨λ“  객체λ₯Ό λŒ€μƒμœΌλ‘œ resolve된 κ°’λ“€μ˜ λ°°μ—΄λ‘œ λ§Œλ“€μ–΄ μ€€λ‹€.
  • Promise.all λ©”μ„œλ“œλŠ” 이런 λ‚΄μš©μœΌλ‘œ κ΅¬μ„±λœ 또 λ‹€λ₯Έ Promise 객체λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ ν•΄μ†Œλœ κ°’λ“€μ˜ 배열은 then λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄μ„œ μ–»λŠ”λ‹€.
  • λ§Œμ•½, 배열에 λ‹΄κΈ΄ Promise 객체 쀑 거절 객체가 λ°œμƒν•˜λ©΄ 더 기닀리지 μ•Šκ³  ν•΄λ‹Ή 거절 값을 담은 Promise.reject 객체λ₯Ό λ°˜ν™˜ν•œλ‹€.
const getAllResolvedResult = <T>(promises: Promise<T>[]) => Promise.all(promises);

getAllResolvedResult<any>([Promise.resolve(true), Promise.resolve('hello')])
.then(result => console.log(result)); // [true, 'hello']

getAllResolvedResult<any>([Promise.reject(new Error('error')), Promise.resolve(1)])
.then(result => console.log(result)) // ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€.
.catch(error => console.log('error: ', error.message)); // error: error

πŸ“š Promise.race λ©”μ„œλ“œβ€‹

  • Promise.race클래슀 λ©”μ„œλ“œλŠ” 배열에 λ‹΄κΈ΄ ν”„λ‘œλ―ΈμŠ€ 객체 쀑 ν•˜λ‚˜λΌλ„ resolve되면 이 값을 담은 Promise.resolve 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. 만일 거절 값이 κ°€μž₯ λ¨Όμ € λ°œμƒν•˜λ©΄ promise.reject 객체λ₯Ό λ°˜ν™˜ν•œλ‹€.
Promise.race([Promise.resolve(true), Promise.resolve('hello')])
.then(value => console.log(value)); // true

Promise.race([Promise.resolve(true), Promise.reject(new Error('hello'))])
.then(value => console.log(value)) // true
.catch(error => console.log(error.message)); // ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€

Promise.race([Promise.reject(new Error('error')), Promise.resolve(true)])
.then(value => console.log(value)) // ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€
.catch(error => console.log(error.message)); // error

πŸ¦„ async와 await ꡬ문​

πŸ“š async ν•¨μˆ˜μ˜ 두 가지 μ„±μ§ˆβ€‹

  • async ν•¨μˆ˜ μˆ˜μ •μžκ°€ 뢙은 ν•¨μˆ˜λŠ” λ‹€μŒκ³Ό 같은 μ„±μ§ˆμ„ 가지고 μžˆλ‹€.
  1. 일반 ν•¨μˆ˜μ²˜λŸΌ μ‚¬μš©ν•  수 μžˆλ‹€.
  2. Promise 객체둜 μ‚¬μš©ν•  수 μžˆλ‹€.

πŸ“š async ν•¨μˆ˜κ°€ λ°˜ν™˜ν•˜λŠ” κ°’μ˜ μ˜λ―Έβ€‹

  • async ν•¨μˆ˜λŠ” 값을 λ°˜ν™˜ν•  수 μžˆλ‹€. μ΄λ•Œ λ°˜ν™˜κ°’μ€ Promise ν˜•νƒœλ‘œ λ³€ν™˜λ˜λ―€λ‘œ λ‹€μŒμ²˜λŸΌ then λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄ async ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ„ μ–»μ–΄μ•Ό ν•œλ‹€.
const asyncReturn = async() => {
return [1, 2, 3];
}

asyncReturn()
.then(value => console.log(value)); // [1, 2, 3]

πŸ“š async ν•¨μˆ˜μ˜ μ˜ˆμ™Έ μ²˜λ¦¬β€‹

const asyncException = async () => {
throw new Error('error');
}

asyncException()
.catch(err => console.log('error: ', err.message)); // error: error

πŸ“š async ν•¨μˆ˜μ™€ Promise.all​

import { readFilePromise } from "./readFilePromise"

const readFilesAll = async (fileNames: string[]) => {
return await Promise.all(
fileNames.map(fileNames => readFilePromise(fileNames))
);
}

readFilesAll(['./package.json', './tsconfig.json'])
.then(([packageJson, tsConfigJson]: string[]) => {
console.log(packageJson);
console.log(tsConfigJson);
})
.catch(err => console.log(err))