๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐ŸŒˆ Chapter 6: ํ”„๋ผ๋ฏธ์Šค ํŒจํ„ด

๐Ÿ“š ๋‹จ์œ„ ํ…Œ์ŠคํŠธโ€‹

๐ŸŽˆ ํ”„๋กœ๋ฏธ์Šค ์‚ฌ์šฉ๋ฒ•โ€‹

5์žฅ์—์„œ recordCheckIn ํ•จ์ˆ˜์—๋Š” checkInRecorder ๊ฐ์ฒด๊ฐ€ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ๋Š” ์žฌ์Šค๋ฏผ ์ŠคํŒŒ์ด๊ฐ€ recordCheckIn ๋Œ€์—ญ์ด์—ˆ๊ณ , ์ด ์ŠคํŒŒ์ด๋Š” ์˜ค์ง recordCheckIn์ด ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€๋งŒ ๊ฐ์‹œํ–ˆ๋‹ค.

expect(checkInRecorder.recordCheckIn).toHaveCalledWith(attendee);

checkInService๊ฐ€ checkInRecorder๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฑด ๋‹จ์ˆœํ•œ ์ €์งˆ๋Ÿฌ ๋†“๊ณ  ์žŠ์–ด๋ฒ„๋ฆฌ๊ธฐ(fire-and-forget)๋ผ ํ˜ธ์ถœ ์—ฌ๋ถ€๋งŒ ์•Œ๋ฉด ๊ทธ๋งŒ์ด๋‹ค.

Conference.checkInService = function(checkInRecorder) {
// ์ฃผ์ž…ํ•œ ใ…ŠheckInRecorder์˜ ์ฐธ์กฐ๊ฐ’์„ ๋ณด๊ด€ํ•œ๋‹ค.
var recorder = checkInRecorder;

return {
checkIn: function(attendee) {
attendee.checkIn();
recorder.recordCheckIn(attendee);
}
};
};

๊ทธ๋Ÿฌ๋‚˜ ๋” ๋งŽ์€ ์ผ์„ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค(์˜ˆ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋˜๋Š” ์ž‘์—… ์„ฑ๊ณต ์‹œ ํ›„์† ์ฒ˜๋ฆฌ๋ฅผ ๋ง๋ถ™์ด๋Š” ์ž‘์—…). checkInRecorder๋Š” ๋ณดํ†ต XMLHttpRequest๋กœ ์ฒดํฌ์ธ ๋“ฑ๋ก ์„œ๋ฒ„์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌํ˜„ํ•œ๋‹ค. checkInRecorder๋Š” ์š”์ฒญ์„ ๋ณด๋‚ธ ํ›„ onreadystatechange ์ด๋ฒคํŠธ๋ฅผ ๊ท€ ๊ธฐ์šธ์ด๊ณ  ์žˆ๋‹ค๊ฐ€ ๊ฒฐ๊ณผ์˜ ์„ฑ๊ณต/์‹คํŒจ์— ๋”ฐ๋ผ ์กฐ์น˜๋ฅผ ํ•œ ๋’ค ์ž์‹ ์˜ ํ˜ธ์ถœ๋ถ€ (checkInService)์— ๋ฐ”ํ†ต์„ ๋„˜๊ธฐ๋Š” ์‹์œผ๋กœ ํ˜๋Ÿฌ๊ฐ„๋‹ค. ์ด๋ ‡๊ฒŒ ํ‹€์— ๋ฐ•ํžŒ ์ž‘์—…์„ ๊ณ„์† ์‹ ๊ฒฝ ์“ฐ๋Š” ๊ฑด ๋”ฐ๋ถ„ํ•œ ์ผ์ด๊ณ  ์ž์นซ ๋‚œ์‚ฝํ•œ ์ฝ”๋“œ๋กœ ๋’ค๋ฒ”๋ฒ… ๋  ์šฐ๋ ค๋„ ์žˆ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ญ”๊ฐ€ ์˜ˆ์˜๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์„๊นŒ?

Conference.checkInService = function(checkInRecorder) {
'use strict';

// ์ฃผ์ž…ํ•œ ใ…ŠheckInRecorder์˜ ์ฐธ์กฐ๊ฐ’์„ ๋ณด๊ด€ํ•œ๋‹ค.
var recorder = checkInRecorder;

return {
checkIn: function(attendee) {
attendee.checkIn();
recorder.recordCheckIn(attendee).then(
// ์„ฑ๊ณต
attendee.setCheckInNumber,
// ์‹คํŒจ
attendee.undoCheckIn,
);
}
};
};

recordCheckIn ๋กœ์ง์„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•œ ๋‹ค์Œ(์ด๋•Œ then์„ ํ˜ธ์ถœ), ๊ทธ ๊ฒฐ๊ณผ์˜ ์„ฑ๊ณต/์‹คํŒจ์— ๋”ฐ๋ผ ์ง€์ •๋œ ์ฝœ๋ฐฑ์„ ๋ถ€๋ฅธ๋‹ค. ์ด๋Ÿฌํ•œ ์š”๊ฑด์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ 5์žฅ์˜ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ค€๋‹ค.

describe('Conference.checkInService', () => {
'use strict';
var checkInService, checkInRecorder, attendee;

beforeEach(() => {
checkInRecorder = Conference.checkInRecorder();
checkInService = Conference.checkInService(checkInRecorder);
attendee = Conference.attendee('ํ˜•์ฒ ', '์„œ');
});

describe('checkInService.checkIn(attendee)', () => {
describe('checkInRecorder ์„ฑ๊ณต ์‹œ', () => {
var checkInNumber = 1234;

beforeEach(() => {
spyOn(checkInRecorder, 'recordCheckIn')
.and
.callFake(() => Promise.resolve(checkInNumber));
});

it('์ฐธ๊ฐ€์ž๋ฅผ ์ฒดํฌ์ธ ์ฒ˜๋ฆฌํ•œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œํ•œ๋‹ค', () => {
checkInService.checkIn(attendee);
expect(attendee.isCheckedIn()).toBe(true);
});

it('์ฒดํฌ์ธ์„ ๋“ฑ๋กํ•œ๋‹ค', () => {
checkInService.checkIn(attendee);
expect(checkInRecorder.recordCheckIn).toHaveBeenCalledWith(attendee);
});

// ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
it('์ฐธ๊ฐ€์ž์˜ checkInNumber๋ฅผ ์ง€์ •ํ•œ๋‹ค', () => {
checkInService.checkIn(attendee);
expect(attendee.getCheckInNumber()).toBe(checkInNumber);
});
});
});
});

์œ„ ์˜ˆ์—์„œ Promise๊ฐ€ ์ด๋ฃจ์–ด์กŒ๊ธฐ์— then ๋ฉ”์„œ๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋˜๊ณ , ์ด ์ฝœ๋ฐฑ์€ ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ํ’ˆ๊ณ  ์žˆ๋Š” ์„ฑ๊ณต์ ์ธ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ(checkInNumber)๋ฅผ ๋„˜๊ฒจ๋ฐ›๋Š”๋‹ค.

์กฐ๋งŒ๊ฐ„ ๋ฒŒ์–ด์งˆ ์ผ๋“ค์„ ์ •๋ฆฌํ•ด๋ณด์ž.

  1. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” checkInService.checkIn์„ ํ˜ธ์ถœํ•œ๋‹ค.
  2. ์ด ๋ฉ”์„œ๋“œ๋Š” recorder.recordCheckIn์„ ํ˜ธ์ถœํ•œ๋‹ค.
  3. recordCheckIn์„ ๊ฐ์‹œ ์ค‘์ธ ์ŠคํŒŒ์ด๋Š” recordCheckIn์ด checkInNumber ๊ฐ’์„ ์ง€๋‹Œ ๊ท€๊ฒฐ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์กฐ์ž‘ํ•œ๋‹ค.
  4. ์ด์–ด์„œ recordCheckIn(attendee).then์˜ ์„ฑ๊ณต ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋œ๋‹ค.
  5. ์„ฑ๊ณต ์ฝœ๋ฐฑ attendee.setCheckInNumber๋Š” checkInNumber๋ฅผ ํ”ผ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค.
  6. ๊ฒฐ๊ตญ, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋งˆ์ง€๋ง‰ ์ค„์˜ ๊ธฐ๋Œ€์‹์€ ๋งž์•„ ๋–จ์–ด์ง„๋‹ค!

ํ•˜์ง€๋งŒ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•œ๋‹ค.

  • Promise๋Š” ๋น„๋™๊ธฐ์ ์ด๋‹ค.
  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ์ด๋ฒคํŠธ ๋ฃจํ”„๋กœ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ์„ ๋ชจ๋ฐฉํ–ˆ์ง€๋งŒ, ์–ด๋””๊นŒ์ง€๋‚˜ ์‹ฑ๊ธ€์Šค๋ ˆ๋“œ ๋ฐฉ์‹์œผ๋กœ ์›€์ง์ธ๋‹ค.

์ฆ‰, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ๋‹ค์Œ ์ฐจ๋ก€๊ฐ€ ์˜ค๊ธฐ ์ „๊นŒ์ง€๋Š” Promise์˜ then ๋ฉ”์„œ๋“œ์— ๊ตฌํ˜„๋œ ์„ฑ๊ณต ์ฝœ๋ฐฑ์œผ๋กœ ํ”„๋กœ๊ทธ๋žจ ์ œ์–ด๊ถŒ์ด ๋„˜์–ด๊ฐˆ ๋ฆฌ ์—†๋‹ค. ๋‹ค์‹œ ๋งํ•ด, ์ด๋ฏธ ๊ธฐ๋Œ€์‹ ํ‰๊ฐ€๋Š” ์˜›๋‚ ์— ๋๋‚œ ํ„ฐ๋ผ Promise ๊ท€๊ฒฐ ์‹œ์ ์—๋Š” ๋„ˆ๋ฌด ๋Šฆ์–ด๋ฒ„๋ฆฐ ๊ผด์ด๋‹ค.

์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ณ ์ณ์•ผ ํ• ๊นŒ? ์ฒซ์งธ, checkInService.checkIn์ด then์„ ํ˜ธ์ถœํ•˜์—ฌ ์ˆ˜์‹ ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ•œ๋‹ค. ๊ท€๊ฒฐ/์„ฑ๊ณต ์ฝœ๋ฐฑ์œผ๋กœ ๋๋‚˜๋ฉด ๊ท€๊ฒฐ Promise๋ฅผ, ๋ฒ„๋ฆผ/์‹คํŒจ ์ฝœ๋ฐฑ์— ์ด๋ฅด๋ฉด ๋ฒ„๋ฆผ Promise๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ด๋‹ค.

var Conference = Conference || {};

Conference.checkInService = function(checkInRecorder) {
'use strict';

// ์ฃผ์ž…ํ•œ ใ…ŠheckInRecorder์˜ ์ฐธ์กฐ๊ฐ’์„ ๋ณด๊ด€ํ•œ๋‹ค.
var recorder = checkInRecorder;

return {
checkIn: function(attendee) {
attendee.checkIn();
recorder.recordCheckIn(attendee).then(
function onRecordCheckInSucceeded(checkInNumber) {
attendee.setCheckInNumber(checkInNumber);
return Promise.resolve(checkInNumber);
},
function inRecordCheckInFailed(reason) {
attendee.undoCheckIn();
return Promise.reject(reason);
}
);
}
};
};

๋‘˜์งธ, then์ด ๋‚ด์–ด์ค€ Promise๊ฐ€ ํ•ด๊ฒฐ๋˜๊ธฐ ์ „์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ์ž์‹ ์˜ ๊ธฐ๋Œ€์‹์„ ํ‰๊ฐ€ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰์•„์•ผ ํ•œ๋‹ค. ๊ธฐ๋Œ€์‹์„ then๋‚ด๋ถ€์—์„œ ์‹คํ–‰์‹œํ‚ค๋ฉด ๋œ๋‹ค.

it('์ฐธ๊ฐ€์ž์˜ ์ฒดํฌ์ธ ๋ฒˆํ˜ธ๋ฅผ ์„ธํŒ…ํ•œ๋‹ค', (done) => {
checkInService.checkIn(attendee).then(
function onPromiseResolved() {
expect(attendee.getCheckInNumber()).toBe(checkInNumber);
done();
},
function onPromiseRejected() {
expect('์ด ์‹คํŒจ ๋ถ„๊ธฐ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋๋‹ค').toBe(false);
done(); // ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋‹ค ๋๋‚˜๋ฉด ๋ฐ˜๋“œ์‹œ ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํƒ€์ž„์•„์›ƒ ์—๋Ÿฌ
},
);
});

๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ํ•ญ์ƒ ์žฌ์Šค๋ฏผ์˜ done()์„ ์จ๋ผ.

๋‹ค์Œ์€ ์‹คํŒจํ•˜๋Š” ๋ถ„๊ธฐ ๋กœ์ง์„ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ๋‹ค.

describe('checkInRecorder ์‹คํŒจ ์‹œ', () => {
var recorderError = '์ฒดํฌ์ธ ๋“ฑ๋ก ์‹คํŒจ!';

beforeEach(() => {
spyOn(checkInRecorder, 'recordCheckIn').and.returnValue(
Promise.reject(new Error(recorderError)),
);
spyOn(attendee, 'undoCheckIn');
});

it('๊ธฐ๋Œ€ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ๋ฒ„๋ฆผ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.', () => {
checkInService.checkIn(attendee).then(
function promiseResolved() {
expect('์ด ์„ฑ๊ณต ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.').toBe(false);
done();
},
function promiseRejected(reason) {
expect(reason.message).toBe(recorderError);
done();
},
);
});
});

๐ŸŽˆ ํ”„๋กœ๋ฏธ์Šค ์ƒ์„ฑ๊ณผ ๋ฐ˜ํ™˜โ€‹

์Šนํ˜„์€ checkInRecorder๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ๋‹ค. ๊ทธ๋Š” ํ˜„์žฌ ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌ์ƒํ•œ๋‹ค.

describe('Conference.checkInRecorder', () => {
'use strict';

var attendee, checkInRecorder;

beforeEach(() => {
attendee = Conference.attendee('Tom', 'Jones');
checkInRecorder = Conference.checkInRecorder();
});

describe('recordCheckIn(attendee)', () => {
it('์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธ๋˜๋ฉด checkInNumber๋กœ ๊ท€๊ฒฐ๋œ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค', (done) => {
attendee.checkIn();
checkInRecorder.recordCheckIn(attendee).then(
function promiseResolved(actualCheckInNumber) {
expect(typeof actualCheckInNumber).toBe('number');
done();
},
function promiseRejected() {
expect('ํ”„๋กœ๋ฏธ์Šค๋Š” ๋ฒ„๋ ค์กŒ๋‹ค').toBe(false);
done();
},
);
});

it('์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธ๋˜์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ์™€ ๋ฒ„๋ฆผ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค', (done) => {
checkInRecorder.recordCheckIn(attendee).then(
function promiseResolved() {
expect('ํ”„๋ผ๋ฏธ์Šค๋Š” ๊ท€๊ฒฐ๋๋‹ค').toBe(false);
done();
},
function promiseRejected(reason) {
expect(reason instanceof Error).toBe(true);
expect(reason.message)
.toBe(checkInRecorder.getMessages().mustBeCheckedIn)
done();
},
);
});
});
});

์ด์ œ ํ…Œ์ŠคํŠธ์— ์„ฑ๊ณตํ•˜๋„๋ก checkInRecorder๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

var Conference = Conference || {};

Conference.checkInRecorder = function() {
'use strict';

var messages = {
mustBeCheckedIn: '์ฐธ๊ฐ€์ž๋Š” ์ฒดํฌ์ธ๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค.',
};

return {
getMessages: function() {
return messages;
},
recordCheckIn: function(attendee) {
return new Promise(function(resolve, reject) {
if (attendee.isCheckedIn()) {
resolve(4444); // ์ผ๋‹จ ์•„๋ฌด ์ˆซ์ž๋‚˜ ๋„ฃ๋Š”๋‹ค.
} else {
reject(new Error(messages.mustBeCheckedIn));
}
});
},
};
};

์Šนํ˜„์€ ์ž ์‹œ ๊ณ ๊ฐœ๋ฅผ ๊ฐธ์šฐ๋šฑํ•˜๋”๋‹ˆ ์ฝœ๋ฐฑ์—์„œ Promise.resolve์™€ Promise.reject๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ง๊ณ  ์•„์˜ˆ ์ฒ˜์Œ๋ถ€ํ„ฐ checkInService.checkIn์—์„œ ์ง์ ‘ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋งŒ๋“ค์–ด๋„ ๋˜์ง€ ์•Š๋‚˜ ์ƒ๊ฐํ•œ๋‹ค.

var Conference = Conference || {};

Conference.checkInService = function(checkInRecorder) {
'use strict';

// ์ฃผ์ž…ํ•œ ใ…ŠheckInRecorder์˜ ์ฐธ์กฐ๊ฐ’์„ ๋ณด๊ด€ํ•œ๋‹ค.
var recorder = checkInRecorder;

return {
checkIn: function(attendee) {
return new Promise(function checkInPromise(resolve, reject) {
attendee.checkIn();
recorder.recordCheckIn(attendee).then(
function onRecordCheckInSucceeded(checkInNumber) {
attendee.setCheckInNumber(checkInNumber);
resolve(checkInNumber);
},
function inRecordCheckInFailed(reason) {
attendee.undoCheckIn();
reject(reason);
}
);
});
}
};
};

๐ŸŽˆ XMLHttpRequest ํ…Œ์ŠคํŒ…โ€‹

์ด์ œ recordCheckIn์˜ XMLHttpRequest๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
์žฌ์Šค๋ฏผ์—์„œ XMLHttpRequest๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ jasmine-ajax๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

describe('Conference.checkInRecorder', () => {
'use strict';

var attendee, checkInRecorder;

beforeEach(() => {
attendee = Conference.attendee('์ผ์›…', '์ด');
attendee.setId(777);
checkInRecorder = Conference.checkInRecorder();

// *** 1 ***
// ์žฌ์Šค๋ฏผ XMLHttpRequest ๋ชจ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜
jasmine.Ajax.install();
});

afterEach(() => {
// ๋‹ค ๋๋‚œ ํ›„์—๋Š” ์›๋ž˜ XMLHttpRequest๋กœ ๋Œ๋ ค๋†“๋Š”๋‹ค.
jasmine.Ajax.uninstall();
});

describe('recordCheckIn(attendee)', () => {
// *** 9 ***
it('HTTP ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜์—ฌ ์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธ๋˜๋ฉด checkInNumber๋กœ ๊ท€๊ฒฐ๋œ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค', () => {
var expectedCheckInNumber = 1234;
var request;

attendee.checkIn();

// *** 2 ***
checkInRecorder.recordCheckIn(attendee).then(
function promiseResolved(actualCheckInNumber) {
// *** 8 ***
expect(actualCheckInNumber).toBe(expectedCheckInNumber);
},
function promiseRejected() {
expect('ํ”„๋กœ๋ฏธ์Šค๋Š” ๋ฒ„๋ ค์กŒ๋‹ค').toBe(false);
},
);

// *** 4 ***
request = jasmine.Ajax.requests.mostRecent();

// *** 5 ***
expect(request.url).toBe('/checkin/' + attendee.getId());

// *** 6 ***
request.response({
'status': 200,
'contextType': 'text/plain',
'responseText': expectedCheckInNumber,
});
});

it('HTTP ์š”์ฒญ์ด ์‹คํŒจํ•˜์—ฌ ์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธ๋˜์ง€ ์•Š์œผ๋ฉด ์ •ํ™•ํ•œ ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ๋ฒ„๋ ค์ง„ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค', () => {
var request;
attendee.checkIn();
checkInRecorder.recordCheckIn(attendee).then(
function promiseResolved(actualCheckInNumber) {
expect('ํ”„๋กœ๋ฏธ์Šค๋Š” ๊ท€๊ฒฐ๋๋‹ค').toBe(false);
},
function promiseRejected(reason) {
expect(reason instanceof Error).toBe(true);
expect(reason.message)
.toBe(checkInRecorder.getMessages().httpFailure);
}
);

request = jasmine.Ajax.requests.mostRecent();
expect(request.url).toBe('/checkin/' + attendee.getId());
request.response({
'status': 404,
'contentType': 'text/plain',
'responseText': '์ด๋ž˜์„œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ์Šต๋‹ˆ๋‹ค.',
});
});

it('์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธ๋˜์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ์™€ ๋ฒ„๋ฆผ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค', (done) => {
checkInRecorder.recordCheckIn(attendee).then(
function promiseResolved() {
expect('ํ”„๋กœ๋ฏธ์Šค๋Š” ๊ท€๊ฒฐ๋๋‹ค').toBe(false);
done();
},
function promiseRejected(reason) {
expect(reason instanceof Error).toBe(true);
expect(reason.message)
.toBe(checkInRecorder.getMessages().mustBeCheckedIn);
done();
},
);
});
});
});

๋‹น์žฅ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ „๋ถ€ ์‹คํŒจํ•  ํ…Œ๋‹ˆ XMLHttpRequest๋ฅผ checkInRecorder์— ์จ๋„ฃ๋Š”๋‹ค.

var Conference = Conference || {};

Conference.checkInRecorder = function() {
'use strict';

var messages = {
mustBeCheckedIn: '์ฐธ๊ฐ€์ž๋Š” ์ฒดํฌ์ธ๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค.',
httpFailure: 'HTTP ์š”์ฒญ ์‹คํŒจ!',
};

return {
getMessages: function() {
return messages;
},
recordCheckIn: function(attendee) {
return new Promise(function(resolve, reject) {
if (attendee.isCheckedIn()) {
// *** 3 ***
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function onreadystatechange() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
// *** 7 ***
resolve(xhr.responseText);
} else {
reject(new Error(messages.httpFailure));
}
}
};

xhr.open('POST', '/checkin/' + attendee.getId(), true);
xhr.send();
} else {
reject(new Error(messages.mustBeCheckedIn));
}
});
},
};
};

๐Ÿ“š ํ”„๋ผ๋ฏธ์Šค ์ฒด์ด๋‹โ€‹

์ƒฌ๋Ÿฟ: ์ด๋ ‡๊ฒŒ ์ฝ”๋”ฉํ•˜๋ฉด ์ค„์ค„์ด ์ฒด์ด๋‹ํ•  ์ˆ˜ ์žˆ๊ฒ ๋Š”๋ฐ? then์ด ๊ท€๊ฒฐ ์ฝ”๋ฐฑ ๋งˆ๋‹ค ์ž๊ธฐ ์•ž์— ์žˆ๋Š” then์ด ์„ฑ๊ณตํ•  ๋–„๋งŒ ์‹คํ–‰๋˜๊ฒŒ ํ•˜๋ฉด ๋˜์ž–์•„?
์Šนํ˜„: ๋ฌผ๋ก  ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆœ ์žˆ์ง€๋งŒ, ๊ทธ๋Ÿฌ๋ฉด ๋‹จ๊ณ„๋งˆ๋‹ค ๋‘ ๋ฒˆ์งธ(๋ฒ„๋ฆผ) ์ฝœ๋ฐฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์„๊นŒ? ๊ฒŒ๋‹ค๊ฐ€ XMLHttpRequest ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฟ ์„ค์ •ํ•ด์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผ ํ• ์ง€๋„ ๋ชฐ๋ผ.
์ƒฌ๋Ÿฟ: ๋งž์•„ ๊ทธ๋Ÿฌ๋„ค. ๊ทผ๋ฐ ๋ง์ด์•ผ, ๋งŒ์ผ ํ•œ ๋‹จ๊ณ„๋ผ๋„ return์ด ๋น ์ง€๋ฉด, ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ undefined๊ฐ€ ๋„˜์–ด์˜ค๊ฒ ์ง€? ์ด๋Ÿฐ ์‹ค์ˆ˜๊ฐ€ ์ œ๋ฒ• ์ž์ฃผ ์ผ์–ด๋‚  ๊ฒƒ ๊ฐ™์€๋ฐ?
์Šนํ˜„: ์‘ ๊ทธ๋ž˜์„œ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‹ค์ œ checkInNumber๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๋Š” ๊ฑฐ์ž–์•„?
์ƒฌ๋Ÿฟ: ๊ธ€์Ž„, ๊ทธ๋ ‡๊ฒŒ ํ•œ๋‹ค๊ณ  ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌ๋  ๊ฒƒ ๊ฐ™์ง€ ์•Š์€๋ฐ? ์˜ˆ๋ฅผ ๋“ค์–ด, ๋งŒ์— ํ•˜๋‚˜ jasmine.Ajax ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ฉด ์–ด์ฐŒ ๋˜๊ฒ ์–ด? ๊ทธ๋ž˜์„œ ๋ง์ธ๋ฐ, Promise์— ๋ฒ”์šฉ์ ์œผ๋กœ ์“ธ ๋งŒํ•œ ๋ชจ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐ™์€ ๊ฑด ์—†์„๊นŒ?

  • ์ƒฌ๋Ÿฟ์€ ์ •๋‹ต์„ ์ฐพ์•˜๋‹ค.

๐Ÿ“š ํ”„๋ผ๋ฏธ์Šค ๋ž˜ํผโ€‹

์ƒฌ๋Ÿฟ: ๋„ค๊ฐ€ ์ง  ์ฝ”๋“œ๋Š” ๋‹จ์ˆœ ๋ฌด์‹ํ•˜์ง€๋งŒ Promise๋ฅผ ๊ท€๊ฒฐ์‹œํ‚ฌ ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋  ๊ฑฐ์•ผ. ์ด๋ฏธ ๊ท€๊ฒฐ๋œ ์ƒํƒœ์—์„œ Promise๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑธ ๋งํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๊ณ , ์ด๋ฏธ ๊ทธ๊ฑด ๋„ค๊ฐ€ ํ•œ ์ผ์ด๋‹ˆ๊นŒ, ์ด๋ฏธ ์žˆ๋Š” Promise์˜ ๊ท€๊ฒฐ/๋ฒ„๋ฆผ ๋ง์ด์•ผ.
์Šนํ˜„: ๊ทธ๊ฒƒ๊นŒ์ง„ ๋ชฐ๋ž๋„ค. ๊ทธ๋Ÿฐ๋ฐ ์ง€๊ธˆ ๋ณด๋‹ˆ Promise ํ”„๋กœํ† ํƒ€์ž…์— then๊ณผ catch ๋‘ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ?
์ƒฌ๋Ÿฟ: ๋ฐ”๋กœ ๊ทธ๊ฑฐ์•ผ. ํ•˜์ง€๋งŒ ๋ฒŒ์จ ๋˜‘๋˜‘ํ•œ ์‚ฌ๋žŒ๋“ค์ด ์œ ์‚ฌ Promise ๊ฐ์ฒด๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์ฃผ์ž…ํ•˜๋Š”, Deferred ๊ฐ™์€ ๋ž˜ํผ๋ฅผ ๋งŒ๋“ค์–ด๋†จ๋”๋ผ๊ณ . ์ด๊ฑธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์ง„์งœ ํ”„๋ผ๋ฏธ์Šค๋‚˜ ๊ฐ€์งœ ํ”„๋ผ๋ฏธ์Šค์ฒ˜๋Ÿผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ ๊ฐ™์•„.

// ์•ต๊ทค๋Ÿฌ 1.3์—์„œ Q๋ผ๋Š” ํ”„๋ผ๋ฏธ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
var deferred = $q.defer(); // ๋ฏธ๋ค„์ง„ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
var promise = deferred.promise; // promise๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๊ฐ€ ์žˆ๋‹ค.
deferred.resolve(1234); // 1234 ๊ฐ’์œผ๋กœ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๊ท€๊ฒฐํ•œ๋‹ค.

๐Ÿ“š ์ƒํƒœ์™€ ์ˆ™๋ช…โ€‹

์ƒฌ๋Ÿฟ: Promise๋ฅผ ๋‹ค๋ฃฐ ๋• ๋ง์ด์•ผ. ์ง„์งœ๋“  Deferred ์“ด ๊ฐ€์งœ๋“  Promise์˜ ๊ท€๊ฒฐ๊ณผ ์ด๋ฃธ ์‚ฌ์ด์˜ ๋ฏธ๋ฌ˜ํ•œ ์ฐจ์ด๋ฅผ ์ดํ•ดํ•ด์•ผ ํ•ด. ์ด๋ฃธ Promise๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ Promise.fulfill์ด ์•„๋‹ˆ๋ผ Promise.resolve๋ผ๊ณ  ๋ถ€๋ฅธ๋‹จ ๋ง์ด์ง€. Promise๋Š” ๊ผญ ์–ด๋–ค ๊ฐ’์œผ๋กœ ์ด๋ฃจ์–ด์„œ ๊ท€๊ฒฐ์‹œ์ผœ์•ผ ํ•œ๋‹จ ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด์•ผ. ๋‹ค๋ฅธ Promise๋กœ๋„ ์–ผ๋งˆ๋“ ์ง€ ๊ท€๊ฒฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด. ์ด๋ฏธ ๋ฒ„๋ ค์ง„ Promise๋‚˜ ๋‚˜์ค‘์— ๋ฒ„๋ ค์งˆ Promise๋ผ๋„ ์ƒ๊ด€์—†์–ด. ๊ท€๊ฒฐ๋˜์—ˆ๋‹ค๋Š” ๋ง์€ ํ”„๋ผ๋ฏธ์Šค ์ˆ™๋ช…์ด ์–ด๋Š ํ•œ์ชฝ์œผ๋กœ ๊ฒฐ์ •๋๋‹จ ๋œป์ด๊ฑฐ๋“ . ํ•˜๋‚˜์˜ ๊ฐ’์œผ๋กœ ๋ชป ๋ฐ•ํžˆ๋“ ์ง€, ์•„๋‹ˆ๋ฉด ๋‹ค๋ฅธ ํ”„๋ผ๋ฏธ์Šค์˜ ๊ถ๊ทน์ ์ธ ์ˆ™๋ช…์ด ๋˜๋Š” ๊ฒŒ์ง€.
๊นƒํ—ˆ๋ธŒ๋ฅผ ๋’ค์ €๋ณด๋‹ˆ ์ƒํƒœ์™€ ์šด๋ช…์ด๋ผ๋Š” ์ œ๋ชฉ์œผ๋กœ ์ •๋ฆฌ๊ฐ€ ์ž˜ ๋˜์–ด์žˆ๋Š” ์‚ฌ์ดํŠธ๊ฐ€ ์žˆ๋”๋ผ. (์ฐธ๊ณ )
๊ธฐ๋ณธ์ ์œผ๋กœ Promise๋Š” ์–ธ์ œ๋‚˜ ์„ธ ๊ฐ€์ง€ ์ƒํƒœ(state), ์ฆ‰ ์ด๋ฃธ(fulfilled), ๋ฒ„๋ฆผ(rejected), ๋ณด๋ฅ˜(pending) ์ค‘ ํ•˜๋‚˜์•ผ. ๊ธฐ์ˆ ์ ์œผ๋กœ ๊นŠ๊ฒŒ ๋“ค์–ด๊ฐ€๋ฉด ๋”์šฑ ๋ฏธ์„ธํ•œ ์ฐจ์ด์ ์ด ์žˆ์ง€๋งŒ, ๋Œ€์ฒด๋กœ ์šฐ๋ฆฌ๊ฐ€ ์ง์ž‘ํ•˜๋Š” ๊ทธ๋Œ€๋กœ์•ผ.
์ˆ™๋ช…(fate)์€ ๊ท€๊ฒฐ(resolved)๊ณผ ๋ฏธ๊ฒฐ(unresolved), ๋‘ ๊ฐ€์ง€์•ผ. ๋ฏธ๊ฒฐ ํ”„๋ผ๋ฏธ์Šค์˜ ์ƒํƒœ๋Š” ํ•ญ์ƒ ๋ณด๋ฅ˜์ง€๋งŒ, ๊ท€๊ฒฐ ํ”„๋ผ๋ฏธ์Šค๋Š” ์„ธ ๊ฐ€์ง€ ์ƒํƒœ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋  ์ˆ˜ ์žˆ์ง€. ๋ฌผ๋ก  ์ด๋ฃธ ์ƒํƒœ๊ฐ€ ์ œ์ผ ์ผ๋ฐ˜์ ์ด์ง€๋งŒ.
then์—์„œ ๋ฒ„๋ฆผ ์ฒ˜๋ฆฌํ•  Promise๋ฅผ ๋ฐ˜ํ™˜ํ•ด๋„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๊ท€๊ฒฐ ๋ถ€๋ถ„์œผ๋กœ ํ˜๋Ÿฌ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์ด ์šฐ๋ฆฌ ์ง๊ด€์— ๋‹ค์†Œ ๋ฐ˜ํ•˜๋Š” ๊ฒƒ ๊ฐ™์ง€๋งŒ, ์ด ๋ฒ„๋ฆผ ์ฝœ๋ฐฑ์ด ๊ท€๊ฒฐ ํ”„๋ผ๋ฏธ์Šค๋‚˜ (๋‹ค์Œ์— ๊ท€๊ฒฐ ํ”Œ๋ฏธ์Šค๋กœ ๋ฐ”๋€”) ๋นˆ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ผ์€ ๊ฐ€๋Šฅํ•˜๊ฒ ์ง€.

๐Ÿ“š ํ‘œ์ค€ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ์ œ์ด์ฟผ๋ฆฌ ํ”„๋ผ๋ฏธ์Šค์™€ ๋‹ค๋ฅธ ์ โ€‹

์ƒฌ๋Ÿฟ: ์šฐ๋ฆฌ๊ฐ€ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ ์ œ์ด์ฟผ๋ฆฌ ํ”„๋ผ๋ฏธ์Šค๋ž‘ ๊ทธ๋ƒฅ Promise๊ฐ€ ๋˜‘๊ฐ™์€ ๊ฒŒ ์•„๋‹ˆ๋”๋ผ๊ณ . (์ฐธ๊ณ )

๐Ÿ“š ์ •๋ฆฌํ•˜๊ธฐโ€‹

Promise๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ๋‹ค.

  • Promise๋Š” ๋‚˜์ค‘์— ๋ฒŒ์–ด์งˆ ์ด๋ฒคํŠธ์™€ ๊ทธ ์„ฑ๊ณต/์‹คํŒจ์— ๋”ฐ๋ผ ๊ฐ๊ธฐ ์‹คํ–‰ํ•  ์ฝœ๋ฐฑ์„ ์บก์Šํ™”ํ•œ ์žฅ์น˜๋‹ค.
  • Promise ์ƒ์„ฑ์ž์˜ ์ธ์ž๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ฐ์‹ผ ํ•จ์ˆ˜๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ๋‘ ์ธ์ž, ์ฆ‰ resolve์™€ reject๋ฅผ ๋ฐ›๋Š”๋‹ค. Promise๊ฐ€ ๊ท€๊ฒฐ ๋˜๋Š” ๋ฒ„๋ฆผ ์ฒ˜๋ฆฌ๋  ๋•Œ ๋‘˜ ์ค‘ ํ•œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.
  • Promise๊ฐ์ฒด์˜ ํ•ต์‹ฌ์€ then ๋ฉ”์„œ๋“œ๋กœ, ์ฝœ๋ฐฑ ํ•จ์ˆ˜ 2๊ฐœ๋ฅผ ์ธ์ž๋กœ ์ทจํ•œ๋‹ค.
  • Promise๊ฐ€ ๊ท€๊ฒฐ๋˜๋ฉด ์ฒซ ๋ฒˆ์งธ ์ฝœ๋ฐฑ์œผ๋กœ ์ด์–ด์ง€๊ณ , ์ด ์ฝœ๋ฐฑ์€ ๊ท€๊ฒฐ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค.
  • Promise๊ฐ€ ๋ฒ„๋ ค์ง€๋ฉด ๋‘ ๋ฒˆ์งธ ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜๊ณ  ๋ฒ„๋ฆผ ์‚ฌ์œ ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค. ๋ฒ„๋ฆผ ์‚ฌ์œ ๋Š” ๋ณดํ†ต Error ๊ฐ์ฒด๋กœ ๋ฐ›์ง€๋งŒ, ๋‹จ์ˆœ ๋ฌธ์ž์—ด๋„ ์ƒ๊ด€์—†๋‹ค.

ํ”„๋ผ๋ฏธ์Šค ๊ธฐ๋ฐ˜ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์กฐ์‹ฌํ•  ํ•จ์ •

  • ๋น„๋™๊ธฐ๋กœ ์ž‘๋™ํ•˜๋Š” ํ”„๋ผ๋ฏธ์Šค๋Š” ์กฐ์‹ฌํ•˜์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ ๊ธฐ๋Œ€์‹์ด ์‹คํ–‰๋  ๋•Œ ์—ฌ์ „ํžˆ ๋ฏธ๊ฒฐ ์ƒํƒœ๋กœ ๋‚จ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์‹คํŒจํ•ด์•ผ ํ•  ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ˆˆ์†์ž„ํ•œ๋‹ค. ์žฌ์Šค๋ฏผ์€ ํŠน๋ณ„ํžˆ ๋น„๋™๊ธฐ ์ฝ”๋“œ ํ…Œ์ŠคํŒ…์„ done() ํ•จ์ˆ˜๋กœ ์ง€์›ํ•œ๋‹ค.
  • XMLHttpRequest๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ๋น„๋™๊ธฐ์ ์ธ HTTP ํŠน์„ฑ์„ ํ‰๋‚ด ๋‚ด๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์žฌ์Šค๋ฏผ์—์„œ ์ œ๊ณตํ•˜๋Š” AJAX ๋ชจ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • Promise๋Š” ๊ตฌ์กฐ์ƒ ์ฒด์ด๋‹์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ๋ชจ๋‘ ๋”ฐ์ ธ๋ณด๊ณ  ์˜๋„ํ–ˆ๋–ค then ์ฝœ๋ฐฑ์œผ๋กœ ์‹คํ–‰ ํ๋ฆ„์ด ์ด๋ฃจ์–ด์ง€๋Š”์ง€ ํ™•์ธํ•˜๋ผ.
  • ์•ต๊ทค๋ŸฌJS์˜ $q๋‚˜ ํฌ๋ฆฌ์Šค ์ฝ”์™ˆ์˜ Q ๊ฐ™์€ ํ”„๋ผ๋ฏธ์Šค ๋ž˜ํผ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ํ”„๋ผ๋ฏธ์Šค ๊ท€๊ฒฐ/๋ฒ„๋ฆผ์„ ๋” ํšจ๊ณผ์ ์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค.