# Promise

ํ”„๋ผ๋ฏธ์Šค๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ์ž‘์—… ์ฒ˜๋ฆฌ์— ์žˆ์–ด์„œ ๊ธฐ์กด์˜ ์ฝœ๋ฐฑ(Callback) ํ•จ์ˆ˜ ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ ์„ (opens new window) ๊ฐœ์„ ํ•œ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

# ํ”„๋ผ๋ฏธ์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ 

๋น„๋™๊ธฐ์  ์ž‘์—… ์ฒ˜๋ฆฌ์— ์žˆ์–ด์„œ ๊ธฐ์กด์˜ ์ฝœ๋ฐฑ ๋ฐฉ์‹์—์„œ ๋ฒ—์–ด๋‚˜ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ์ฝœ๋ฐฑ ์ง€์˜ฅ์˜(Pyramid of Doom) ํ•ด๊ฒฐ
  2. ์—๋Ÿฌ์˜ ์ฒ˜๋ฆฌ์˜ ์šฉ์ด์„ฑ

# 1. ์ฝœ๋ฐฑ ์ง€์˜ฅ(Pyramid of Doom)

๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ž‘์—…์ด ๋‘˜ ์ด์ƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ฝœ๋ฐฑ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

loadScript('1.js', function(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ...
        loadScript('2.js', function(error, script) {
            if (error) {
                handleError(error);
            } else {
                // ...
                loadScript('3.js', function(error, script) {
                    if (error) {
                        handleError(error);
                    } else {
                        // ...continue after all scripts are loaded (*)
                    }
                });
            }
        });
    }
});

์ „์ฒด์ ์ธ ์ฝ”๋“œ์˜ ํ๋ฆ„์„ ์ •๋ฆฌํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. 1.jsํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ณผ์ •์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  2. 1.jsํŒŒ์ผ์„ ์ •์ƒ์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์™”๋‹ค๋ฉด, 2.js ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  3. 2.jsํŒŒ์ผ์„ ์ •์ƒ์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์™”๋‹ค๋ฉด, 3.js ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ž‘์—…์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๋พฐ์กฑํƒ‘์ฒ˜๋Ÿผ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์น˜์šฐ์น˜๋Š” ํ˜•ํƒœ๋ฅผ ๋ณด์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜ ์ค‘์ฒฉ์˜ ๋ชจ์–‘์ด ํ”ผ๋ผ๋ฏธ๋“œ์™€ ๋น„์Šทํ•˜๋‹ค๊ณ  ํ•˜์—ฌ Pyramid of Doom์œผ๋กœ ๋ถˆ๋ฆฌ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์‹œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ ์ฝœ๋ฐฑ ์ง€์˜ฅ์€ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ์ €ํ•ดํ•˜๊ฒŒ ๋˜์ง€๋งŒ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

# 2. ์—๋Ÿฌ์˜ ์ฒ˜๋ฆฌ

์‚ฌ์‹ค ์ฝœ๋ฐฑ ์ง€์˜ฅ์€ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ™œ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ต๋ช… ํ•จ์ˆ˜์˜ ์‚ฌ์šฉ์„ ํฌ๊ธฐํ•˜๊ณ  ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋“ค์„ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

loadScript('1.js', step1);

function step1(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ..
        loadScript('2.js', step2);
    }
}

function step2(error, script) {
    if (error) {
        handleError(error);
    } else {
        // ..
        loadScript('3.js', step3);
    }
}

// step3, step4 ....

์œ„์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ๋ถ„๋ฆฌ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Œ์—๋„ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ๋” ๋ฐ”๋žŒ์งํ•œ ์ด์œ ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์‰ฝ๋‹ค๋Š” ์ธก๋ฉด์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ†ตํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ๋’ค์—์„œ ์„ค๋ช…ํ•  .then, .catch ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„๋‹จํžˆ ์ •๋ฆฌ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

function loadScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;

        script.onload = () => resolve(script);
        script.onerror = () => reject(new Error('Error!'));

        document.head.append(script);
    });
}

loadScript('callback.js')
    .then(console.log)
    .catch(console.log);

# ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

์ƒ์„ฑ์ž ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•œ ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด ์ƒ์„ฑ ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

const promise = new Promise((resolve, reject) => {
    // executor
}

Promise()์ƒ์„ฑ์ž์— ์ „๋‹ฌ๋˜๋Š” ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ํ•จ์ˆ˜(executor)๋กœ, ๊ฐ์ฒด ์ƒ์„ฑ ํ›„ ์ž๋™์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

# ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ

ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด๋Š” ๋‘ ๊ฐ€์ง€ ํ”„๋กœํผํ‹ฐ(properties)๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

  1. ์ƒํƒœ(state): pending์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜๋ฉฐ resolve๊ฐ€ ํ˜ธ์ถœ๋  ์‹œ fulfilled๋กœ, reject๊ฐ€ ํ˜ธ์ถœ๋  ์‹œ rejected๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค. rejected, resolved ๋‘ ์ƒํƒœ๋ฅผ ํ†ต์นญํ•˜์—ฌ settled ์ƒํƒœ ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

  2. ๊ฒฐ๊ณผ(result): undefined๋กœ ์ดˆ๊ธฐํ™”๋˜๋ฉฐ resolve(value)๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ์‹œ value๋กœ, reject(error)๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ์‹œ error๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค.

# ๊ธฐ๋ณธ ์˜ˆ์ œ

๋‹ค์Œ์˜ ์˜ˆ์ œ๋Š” ๋ณ„๋„์˜ ํ•„ํ„ฐ๋ง ์—†์ด ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ๋’ค console.log(promise)๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

const promise = new Promise(function(resolve, reject) {
    setTimeout(() => {
        resolve('success');
    }, 1000);
});
console.log(promise);

ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด์˜ ์‹คํ–‰ ํ•จ์ˆ˜๋ฅผ 1์ดˆ๊ฐ€ ์ง€๋‚œ ๋’ค์— ์‹คํ–‰ํ•˜๊ฒŒ๋” ์„ค์ •ํ•ด๋‘์—ˆ์œผ๋ฏ€๋กœ, ์œ„์˜ ์˜ˆ์ œ๋ฅผ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด ์ฝ˜์†” ์ƒ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined

์ดํ›„ 1์ดˆ๊ฐ€ ์ง€๋‚œ ์‹œ์ ์—์„œ ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†” ์ƒ์— console.log(promise)๋กœ ๊ฐ์ฒด๋ฅผ ์žฌํ™•์ธํ•œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Promise {<fulfilled>: "success"}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "success"

# ์ฃผ์˜์‚ฌํ•ญ

  1. ํ”„๋ผ๋ฏธ์Šค๊ฐ์ฒด์˜ ์‹คํ–‰ ํ•จ์ˆ˜๋Š” ๋‹จ ํ•˜๋‚˜์˜ resolve ๋˜๋Š” reject๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const promise = new Promise((resolve, reject) => {
    resolve('done!');

    reject(new Error('error')); // ignored
    setTimeout(() => {
        resolve('..');
    }, 1000);
});
  1. ํ”„๋ผ๋ฏธ์Šค์˜ reject๋Š” resolve์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ธ์ž์— ์–ด๋– ํ•œ ํƒ€์ž…์ด ์™€๋„ ์ƒ๊ด€์—†์ง€๋งŒ, Error๊ฐ์ฒด์™€ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋‹ค์Œ์˜ ๋ฌธ์„œ (opens new window)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

  2. resolve์™€ reject๋Š” ๊ผญ ๋น„๋™๊ธฐ์ ์œผ๋กœ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.

const promise = new Promise((resolve, reject) => {
    resolve(123);
});
  1. ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด์˜ state์™€ result๋Š” ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. .then, .catch, .finally ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋‹ค๋ค„์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# then, catch, finally

# 1. then

.then ๋ฉ”์„œ๋“œ๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ resolved์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ›๊ณ , ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” rejected์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

promise.then(
    function(result) {
        // resolved!
    },
    function(error) {
        // rejected!
    }
);

# 2. catch

ํ”„๋ผ๋ฏธ์Šค ๊ฐ์ฒด์˜ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ (rejected๋œ ๊ฒฝ์šฐ) ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. .catch๋ฉ”์„œ๋“œ๋Š” .then ๋ฉ”์„œ๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž์— null์„ ์ „๋‹ฌํ•œ ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž‘๋™ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

const asyncThing = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('Error!')), 1000);
});

asyncThing.catch(alert); // same as promise.then(null, alert)

ํ”„๋ผ๋ฏธ์Šค์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ๊ฐ€๊ธ‰์  .catch ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” Javascript.info - Error handling with promises (opens new window)๋ฌธ์„œ์— ์†Œ๊ฐœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

# 3. finally

.finally ๋ฉ”์„œ๋“œ๋Š” ํ”„๋ผ๋ฏธ์Šค๊ฐ€ settled ์ƒํƒœ์ผ ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. Promise ๊ฐ์ฒด ์ •์˜ ํ›„, ์ž‘์—… ์ฒ˜๋ฆฌ์˜ ์„ฑ๊ณต ๋ฐ ์‹คํŒจ ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด, .finally๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

.finally๋Š” ์ธ์ž๋ฅผ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('result!'), 1000);
});
promise
    .then(console.log);
    .finally(() => {
        alert('promise ready!');
    })

# ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ ์˜ˆ์‹œ

๋‹ค์Œ ๋‘ ์˜ˆ์ œ๋Š” ๋ชจ๋‘ ์ œ์ด์ฟผ๋ฆฌ(jquery)์˜ $.get ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ์˜ˆ์ œ ์ฝ”๋“œ๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” $.get ๋ฉ”์„œ๋“œ๋กœ ์ธํ•ด ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ๊ฐ’์„ ๋ณ€์ˆ˜ todo์— ์ €์žฅํ•˜์—ฌ๋„ ๊ฒฐ๊ณผ๋ฌผ์€ undefined๋กœ ๋‚˜ํƒ€๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ๊ฐ’์„ ์ •์ƒ์ ์œผ๋กœ ํ™•์ธํ•˜์ง€ ๋ชปํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

function getTodo() {
    let todo;
    $.get('https://jsonplaceholder.typicode.com/todos/1', function(response) {
        todo = response;
    });
    return todo;
}
console.log(getTodo()); // undefined

์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋ผ๋ฏธ์Šค๋ฅผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

function getTodo() {
    return new Promise((resolve, reject) => {
        $.get('https://jsonplaceholder.typicode.com/todos/1', (response) => {
            resolve(response);
        });
    });
}
getTodo().then(console.log);

์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” getTodo ํ•จ์ˆ˜์—์„œ ํŠน์ • ๋ณ€์ˆ˜๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํ”„๋ผ๋ฏธ์Šค์˜ ์ฝœ๋ฐฑ์ธ resolveํ•จ์ˆ˜์— ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ๊ฐ’์„ ๋‹ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

$.get() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์š”์ฒญ์„ ๋ณด๋‚ธ URL๋กœ๋ถ€ํ„ฐ ์ •์ƒ์ ์œผ๋กœ ์‘๋‹ต์ด ์˜ค๊ณ  ๋‚˜๋ฉด resolve ์ฝœ๋ฐฑ์— ํ•ด๋‹น ์‘๋‹ต ๊ฐ’์ด ๋‹ด๊ฒจ .then ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

์ดํ›„ ์ฝœ๋ฐฑ์— ์ „๋‹ฌ๋œ ์‘๋‹ต ๊ฐ’์„ ์ถœ๋ ฅํ•ด๋ณด๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ด ์ด๋ฃจ์–ด์กŒ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# ํ”„๋ผ๋ฏธ์Šค ์ •์  ๋ฉ”์„œ๋“œ