ë¹ë기 ì´í°ë ì´í°(asynchronous iterator)를 ì¬ì©íë©´ ë¹ë기ì ì¼ë¡ ë¤ì´ì¤ë ë°ì´í°ë¥¼ íìì ë°ë¼ ì²ë¦¬í ì ììµëë¤. ë¤í¸ìí¬ë¥¼ íµí´ ë°ì´í°ê° ì¬ë¬ ë²ì ê±¸ì³ ë¤ì´ì¤ë ìí©ì ì²ë¦¬í ì ìê² ëì£ . ë¹ë기 ì´í°ë ì´í°ì ëíì¬ ë¹ë기 ì ëë ì´í°(asynchronous generator)를 ì¬ì©íë©´ ì´ë° ë°ì´í°ë¥¼ ì¢ ë í¸ë¦¬íê² ì²ë¦¬í ì ììµëë¤.
먼ì ê°ë¨í ìì를 ì´í´ë³´ë©° 문ë²ì ìµí í, ì¤ë¬´ìì ë²ì´ì§ ë²í ì¬ë¡ë¥¼ ê°ì§ê³ async ì´í°ë ì´í°ì ì ëë ì´í°ê° ì´ë»ê² ì¬ì©ëëì§ ììë³´ê² ìµëë¤.
async ì´í°ë ì´í°
ë¹ë기 ì´í°ë ì´í°ë ì¼ë° ì´í°ë ì´í°ì ì ì¬íë©°, ì½ê°ì 문ë²ì ì¸ ì°¨ì´ê° ììµëë¤.
iterable ê°ì²´ ì±í°ìì ì´í´ë³¸ ë°ì ê°ì´ âì¼ë°â ì´í°ë¬ë¸ì ê°ì²´ì ëë¤.
let range = {
from: 1,
to: 5,
// for..of ìµì´ ì¤í ì, Symbol.iteratorê° í¸ì¶ë©ëë¤.
[Symbol.iterator]() {
// Symbol.iteratorë©ìëë ì´í°ë ì´í° ê°ì²´ë¥¼ ë°íí©ëë¤.
// ì´í for..ofë ë°íë ì´í°ë ì´í° ê°ì²´ë§ì ëìì¼ë¡ ëìíëë°,
// ë¤ì ê°ì next()ìì ì í´ì§ëë¤.
return {
current: this.from,
last: this.to,
// for..of ë°ë³µë¬¸ì ìí´ ê° ì´í°ë ì´ì
ë§ë¤ next()ê° í¸ì¶ë©ëë¤.
next() { // (2)
// next()ë ê°ì²´ ííì ê°, {done:.., value :...}를 ë°íí©ëë¤.
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for(let value of range) {
alert(value); // 1, 2, 3, 4, 5
}
ì¼ë° ì´í°ë ì´í°ì ëí ì¤ëª ì iterable ê°ì²´ìì ìì¸í ë¤ë£¨ê³ ìì¼ë, ê¼ ì´í´ë³´ì기 ë°ëëë¤.
ì´ì , ì´í°ë¬ë¸ ê°ì²´ë¥¼ ë¹ë기ì ì¼ë¡ ë§ë¤ë ¤ë©´ ì´ë¤ ìì ì´ íìíì§ ììë´ ìë¤.
Symbol.iteratorëì ,Symbol.asyncIterator를 ì¬ì©í´ì¼ í©ëë¤.next()ë íë¼ë¯¸ì¤ë¥¼ ë°íí´ì¼ í©ëë¤.- ë¹ë기 ì´í°ë¬ë¸ ê°ì²´ë¥¼ ëìì¼ë¡ íë ë°ë³µ ìì
ì
for await (let item of iterable)ë°ë³µë¬¸ì ì¬ì©í´ ì²ë¦¬í´ì¼ í©ëë¤.
ìµìí ììì¸ ì´í°ë¬ë¸ ê°ì²´ range를 í ëë¡, ì¼ì´ë§ë¤ ë¹ë기ì ì¼ë¡ ê°ì ë°ííë ì´í°ë¬ë¸ ê°ì²´ë¥¼ ë§ë¤ì´ë³´ê² ìµëë¤.
let range = {
from: 1,
to: 5,
// for await..of ìµì´ ì¤í ì, Symbol.asyncIteratorê° í¸ì¶ë©ëë¤.
[Symbol.asyncIterator]() { // (1)
// Symbol.asyncIterator ë©ìëë ì´í°ë ì´í° ê°ì²´ë¥¼ ë°íí©ëë¤.
// ì´í for await..ofë ë°íë ì´í°ë ì´í° ê°ì²´ë§ì ëìì¼ë¡ ëìíëë°,
// ë¤ì ê°ì next()ìì ì í´ì§ëë¤.
return {
current: this.from,
last: this.to,
// for await..of ë°ë³µë¬¸ì ìí´ ê° ì´í°ë ì´ì
ë§ë¤ next()ê° í¸ì¶ë©ëë¤.
async next() { // (2)
// next()ë ê°ì²´ ííì ê°, {done:.., value :...}를 ë°íí©ëë¤.
// (ê°ì²´ë asyncì ìí´ ìëì¼ë¡ íë¼ë¯¸ì¤ë¡ ê°ì¸ì§ëë¤.)
// ë¹ëê¸°ë¡ ë¬´ì¸ê°ë¥¼ í기 ìí´ await를 ì¬ì©í ì ììµëë¤.
await new Promise(resolve => setTimeout(resolve, 1000)); // (3)
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
for await (let value of range) { // (4)
alert(value); // 1,2,3,4,5
}
})()
ì ìììì ë³¼ ì ìë¯ì´, async ì´í°ë ì´í°ë ì¼ë° ì´í°ë ì´í°ì êµ¬ì¡°ê° ì ì¬í©ëë¤. íì§ë§ ìëì ê°ì ì°¨ì´ê° ììµëë¤.
- ê°ì²´ë¥¼ ë¹ë기ì ì¼ë¡ ë°ë³µ ê°ë¥íëë¡ íë ¤ë©´,
Symbol.asyncIteratorë©ìëê° ë°ëì 구íëì´ ìì´ì¼ í©ëë¤. â(1) Symbol.asyncIteratorë íë¼ë¯¸ì¤ë¥¼ ë°ííë ë©ìëì¸next()ê° êµ¬íë ê°ì²´ë¥¼ ë°íí´ì¼ í©ëë¤. â(2)next()ëasyncë©ìëì¼ íìë ììµëë¤. íë¼ë¯¸ì¤ë¥¼ ë°ííë ë©ìëë¼ë©´ ì¼ë° ë©ìëë ê´ì°®ìµëë¤. ë¤ë§,async를 ì¬ì©íë©´awaitë ì¬ì©í ì ì기 ë문ì, ì¬ê¸°ì í¸ììasyncë©ìë를 ì¬ì©í´ ì¼ ì´ì ëë ì´ê° ì기ëë¡ íìµëë¤. â(3)- ë°ë³µ ìì
ì íë ¤ë©´ âforâ ë¤ì 'awaitâ를 ë¶ì¸
for await(let value of range)를 ì¬ì©íë©´ ë©ëë¤.for await(let value of range)ê° ì¤íë ërange[Symbol.asyncIterator]()ê° ì¼í í¸ì¶ëëë°, ê·¸ ì´íì ê° ê°ì ëìì¼ë¡next()ê° í¸ì¶ë©ëë¤. â(4)
ì¼ë° ì´í°ë ì´í°ì async ì´í°ë ì´í°ë¥¼ ê°ëµíê² ë¹êµíë©´ ë¤ìê³¼ ê°ìµëë¤.
| ì´í°ë ì´í° | async ì´í°ë ì´í° | |
|---|---|---|
| ì´í°ë ì´í°ë¥¼ ì ê³µí´ì£¼ë ë©ìë | Symbol.iterator |
Symbol.asyncIterator |
next()ê° ë°ííë ê° |
모ë ê° | Promise |
| ë°ë³µ ìì ì ìí´ ì¬ì©íë ë°ë³µë¬¸ | for..of |
for await..of |
...ì ë¹ë기ì ì¼ë¡ ëìíì§ ììµëë¤.ì¼ë°ì ì¸ ë기 ì´í°ë ì´í°ê° íìí 기ë¥ì ë¹ë기 ì´í°ë ì´í°ì í¨ê» ì¬ì©í ì ììµëë¤.
ì ê° êµ¬ë¬¸ì ì¼ë° ì´í°ë ì´í°ê° íìë¡ íë¯ë¡ ìëì ê°ì ì½ëë ëìíì§ ììµëë¤.
alert( [...range] ); // Symbol.iteratorê° ì기 ë문ì ìë¬ ë°ì
ì ê° êµ¬ë¬¸ì awaitê° ìë for..ofì ë§ì°¬ê°ì§ë¡, Symbol.asyncIteratorê° ìë Symbol.iterator를 찾기 ë문ì ìë¬ê° ë°ìíë ê²ì ë¹ì°í©ëë¤.
async ì ëë ì´í°
ìì ë°°ì´ ë°ì ê°ì´ ìë°ì¤í¬ë¦½í¸ìì ì ëë ì´í°ë¥¼ ì¬ì©í ì ìëë°, ì ëë ì´í°ë ì´í°ë¬ë¸ ê°ì²´ì ëë¤.
ì ëë ì´í° ì±í°ìì ì´í´ë³¸ startë¶í° endê¹ì§ì ì°ìë ì«ì를 ìì±í´ì£¼ë ì ëë ì´í°ë¥¼ ë ì¬ë ¤ ë´
ìë¤.
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for(let value of generateSequence(1, 5)) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
ì¼ë° ì ëë ì´í°ìì await를 ì¬ì©í ì ììµëë¤. ê·¸ë¦¬ê³ ëª¨ë ê°ì ë기ì ì¼ë¡ ìì°ë©ëë¤. for..of ì´ëììë ëë ì´ë¥¼ ì¤ ë§í ê³³ì´ ìì£ . ì¼ë° ì ëë ì´í°ë ë기ì 문ë²ì
ëë¤.
ê·¸ë°ë° ì ëë ì´í° 본문ìì await를 ì¬ì©í´ì¼ë§ íë ìí©ì´ ë°ìíë©´ ì´ë»ê² í´ì¼ í ê¹ì? ìëì ê°ì´ ë¤í¸ìí¬ ìì²ì í´ì¼ íë ìí©ì´ ë°ìíë©´ ë§ì´ì£ .
ë¬¼ë¡ ê°ë¥í©ëë¤. ìë ììì ê°ì´ async를 ì ëë ì´í° í¨ì ìì ë¶ì¬ì£¼ë©´ ë©ëë¤.
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// await를 ì¬ì©í ì ììµëë¤!
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
let generator = generateSequence(1, 5);
for await (let value of generator) {
alert(value); // 1, 2, 3, 4, 5
}
})();
ì´ì for await...ofë¡ ë°ë³µì´ ê°ë¥í async ì ëë ì´í°ë¥¼ ì¬ì©í ì ìê² ëììµëë¤.
async ì ëë ì´í°ë¥¼ ë§ëë ê²ì ì¤ì ë¡ë ìë¹í ê°ë¨í©ëë¤. async í¤ìë를 ë¶ì´ê¸°ë§ íë©´ ì ëë ì´í° ììì íë¼ë¯¸ì¤ì 기í async í¨ì를 기ë°ì¼ë¡ ëìíë await를 ì¬ì©í ì ììµëë¤.
async ì ëë ì´í°ì generator.next() ë©ìëë ë¹ë기ì ì´ ëê³ , íë¼ë¯¸ì¤ë¥¼ ë°ííë¤ë ì ì ì¼ë° ì ëë ì´í°ì async ì ëë ì´í°ì ë ë¤ë¥¸ ì°¨ì´ì
ëë¤.
ì¼ë° ì ëë ì´í°ììë result = generator.next()를 ì¬ì©í´ ê°ì ì»ìµëë¤. ë°ë©´ async ì ëë ì´í°ììë ìëì ê°ì´ await를 ë¶ì¬ì¤ì¼ í©ëë¤.
result = await generator.next(); // result = {value: ..., done: true/false}
async ì´í°ë¬ë¸
ì´ë¯¸ ë°°ì ë¯ì´, ë°ë³µ ê°ë¥í ê°ì²´ë¥¼ ë§ë¤ë ¤ë©´ ê°ì²´ì Symbol.iterator를 ì¶ê°í´ì¼ í©ëë¤.
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return <range를 ë°ë³µê°ë¥íê² ë§ëë nextê° êµ¬íë ê°ì²´>
}
}
ê·¸ë°ë° Symbol.iteratorë ì ììì ê°ì´ nextê° êµ¬íë ì¼ë° ê°ì²´ë¥¼ ë°ííë ê² ë³´ë¤, ì ëë ì´í°ë¥¼ ë°ííëë¡ êµ¬ííë ê²½ì°ê° ë ë§ìµëë¤.
ì ëë ì´í° ì±í°ì ìì를 ë¤ì ìê¸°í´ ë´ ìë¤.
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*()를 ì§§ê² ì¤ì
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1, 2, 3, 4, 5
}
ì ìììì 커ì¤í
ê°ì²´ rangeë ë°ë³µ ê°ë¥íê³ , ì ëë ì´í° *[Symbol.iterator]ì ê°ì ëì´í´ì£¼ë ë¡ì§ì´ 구íëì´ ììµëë¤.
ì§ê¸ ìíìì ì ëë ì´í°ì ë¹ë기 ëìì ì¶ê°íë ¤ë©´, Symbol.iterator를 async Symbol.asyncIteratorë¡ ë°ê¿ì¼ í©ëë¤.
let range = {
from: 1,
to: 5,
async *[Symbol.asyncIterator]() { // [Symbol.asyncIterator]: async function*()ì ëì¼
for(let value = this.from; value <= this.to; value++) {
// ê° ì¬ì´ ì¬ì´ì ì½ê°ì 공백ì ì¤
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for await (let value of range) {
alert(value); // 1, 2, 3, 4, 5
}
})();
ì´ì 1ì´ì ê°ê²©ì ëê³ ê°ì ì»ì ì ììµëë¤.
ì¤ì ì¬ë¡
ì§ê¸ê¹ì§ ì주 ê°ë¨í ììë¤ë§ ì´í´ë³´ë©°, async ì ëë ì´í°ì ëí 기ì´ë¥¼ ë¤ì¡ìµëë¤. ì´ì ì¤ë¬´ìì ì í ë²í ì ì¤ ì¼ì´ì¤ë¥¼ ì´í´ë³´ê² ìµëë¤.
ìë¹í ë§ì ì¨ë¼ì¸ ìë¹ì¤ê° íì´ì§ë¤ì´ì (pagination)ì 구íí´ ë°ì´í°ë¥¼ ì ì¡í©ëë¤. ì¬ì©ì 목ë¡ì´ íìí´ì ìë²ì ìì²ì ë³´ë´ë©´, ìë²ë ì¼ì ì«ì(ì를 ë¤ì´ 100ëª ì ì¬ì©ì) ë¨ìë¡ ì¬ì©ì를 ëì´ ì 보를 'í íì´ì§âë¡ êµ¬ì±í í, ë¤ì íì´ì§ë¥¼ ë³¼ ì ìë URLê³¼ í¨ê» ìëµí©ëë¤.
ì´ë° í¨í´ì ì¬ì©ì ëª©ë¡ ì ì¡ë¿ë§ ìëë¼, ë¤ìí ìë¹ì¤ìì ì°¾ìë³¼ ì ììµëë¤. GitHubìì ì»¤ë° ì´ë ¥ì ë³¼ ëë íì´ì§ë¤ì´ì ì´ ì¬ì©ë©ëë¤.
- í´ë¼ì´ì¸í¸ë
https://api.github.com/repos/<repo>/commitsííì URLë¡ ìì²ì ë³´ë ëë¤. - GitHubìì ì»¤ë° 30ê°ì ì ë³´ê° ë´ê¸´ JSONê³¼ í¨ê», ë¤ì íì´ì§ì ëí ì 보를
Linkí¤ëì ë´ì ìëµí©ëë¤. - ë ë§ì ì»¤ë° ì ë³´ê° íìíë©´ í¤ëì ë´ê¸´ ë§í¬ë¥¼ ì¬ì©í´ ë¤ì ìì²ì ë³´ë ëë¤. ìíë ì 보를 ì»ì ëê¹ì§ ì´ë° ê³¼ì ì ë°ë³µí©ëë¤.
ì¤ì GitHub APIë ë³µì¡íì§ë§, ì¬ê¸°ì ì»¤ë° ì ë³´ê° ë´ê¸´ ì´í°ë¬ë¸ ê°ì²´ë¥¼ ë§ë¤ì´ì ìëì ê°ì´ ê°ì²´ë¥¼ ëìì¼ë¡ ë°ë³µ ìì ì í ì ìê² í´ì£¼ë ê°ë¨í API를 ë§ë¤ì´ ë³´ëë¡ íê² ìµëë¤.
let repo = 'javascript-tutorial/en.javascript.info'; // ì»¤ë° ì 보를 ì»ì´ì¬ GitHub 리í¬ì§í 리
for await (let commit of fetchCommits(repo)) {
// ì¬ê¸°ì ê° ì»¤ë°ì ì²ë¦¬í¨
}
íìí ëë§ë¤ ìì²ì ë³´ë´ ì»¤ë° ì 보를 ê°ì ¸ì¤ë í¨ì fetchCommits(repo)를 ë§ë¤ì´ API를 구ì±íëë¡ íê² ìµëë¤. fetchCommits(repo)ìì íì´ì§ë¤ì´ì
ê´ë ¨ ì¼ë¤ì 모ë ì²ë¦¬íëë¡ íë©´ ìíë ëë¡ for await..ofìì ê° ì»¤ë°ì ì²ë¦¬í ì ìì ê²ëë¤.
async ì ëë ì´í°ë¥¼ ì´ì©íë©´ ì½ê² í¨ì를 구íí ì ììµëë¤.
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, { // (1)
headers: {'User-Agent': 'Our script'}, // GitHubë 모ë ìì²ì user-agentí¤ë를 ê°ì í©ëë¤.
});
const body = await response.json(); // (2) ìëµì JSON ííë¡ ìµëë¤(커ë°ì´ ë´ê¸´ ë°°ì´).
// (3) í¤ëì ë´ê¸´ ë¤ì íì´ì§ë¥¼ ëíë´ë URLì ì¶ì¶í©ëë¤.
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage?.[1];
url = nextPage;
for(let commit of body) { // (4) íì´ì§ê° ëë ëê¹ì§ 커ë°ì íëì© ë°í(yield)í©ëë¤.
yield commit;
}
}
}
- ë¤ì´ë¡ëë fetch ë©ìëë¡ íê² ìµëë¤.
fetch를 ì¬ì©íë©´ ì¸ì¦ ì ë³´ë í¤ë ë±ì í¨ê» ì¤ì´ ìì²í ì ììµëë¤. GitHubìì ê°ì íëUser-Agent를 í¤ëì ì¤ì´ ë³´ë´ê² ìµëë¤. fetchì ì© ë©ìëì¸response.json()ì ì¬ì©í´ ìì² ê²°ê³¼ë¥¼ JSONì¼ë¡ íì±í©ëë¤.- ìëµì
Linkí¤ëìì ë¤ì íì´ì§ì URLì ì»ìµëë¤. í¤ëììhttps://api.github.com/repositories/93253246/commits?page=2ííì URLë§ ì¶ì¶í기 ìí´ ì ê·ííìì ì¬ì©íììµëë¤. - 커ë°ì íëì© ë°ííëë°, ì ì²´ ë¤ ë°íëë©´ ë¤ì
while(url)ë°ë³µë¬¸ì´ í¸ë¦¬ê±° ëì´ ìë²ì ë¤ì ìì²ì ë³´ë ëë¤.
ì¬ì©ë²ì ë¤ìê³¼ ê°ìµëë¤(ì½ì ì°½ì ì´ì´ ê° ì»¤ë°ì author를 íì¸í´ë³´ì¸ì).
(async () => {
let count = 0;
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit.author.login);
if (++count == 100) { // 100ë²ì§¸ 커ë°ìì ë©ì¶¥ëë¤.
break;
}
}
})();
ì²ìì 구ìíë APIê° êµ¬íëììµëë¤. íì´ì§ë¤ì´ì ê³¼ ê´ë ¨ë ë´ë¶ ë©ì»¤ëì¦ì ë°ê¹¥ìì ë³¼ ì ìê³ , ì°ë¦¬ë ë¨ìí async ì ëë ì´í°ë¥¼ ì¬ì©í´ ìíë 커ë°ì ë°íë°ê¸°ë§ íë©´ ë©ëë¤.
ìì½
ì¼ë°ì ì¸ ì´í°ë ì´í°ì ì ëë ì´í°ë ë°ì´í°ë¥¼ ê°ì ¸ì¤ë ë° ìê°ì´ ê±¸ë¦¬ì§ ìì ëì ì í©í©ëë¤.
ê·¸ë°ë° ì½ê°ì ì§ì°ì´ ìì´ì ë°ì´í°ê° ë¹ë기ì ì¼ë¡ ë¤ì´ì¤ë ê²½ì° async ì´í°ë ì´í°ì async ì ëë ì´í°, for..ofëì for await..of를 ì¬ì©íê² ë©ëë¤.
ì¼ë° ì´í°ë ì´í°ì async ì´í°ë ì´í°ì ë¬¸ë² ì°¨ì´ë ë¤ìê³¼ ê°ìµëë¤.
| iterable | async iterable | |
|---|---|---|
| iterator를 ë°ííë ë©ìë | Symbol.iterator |
Symbol.asyncIterator |
next()ê° ë°ííë ê° |
{value:â¦, done: true/false} |
{value:â¦, done: true/false}를 ê°ì¸ë Promise |
ì¼ë° ì ëë ì´í°ì async ì ëë ì´í°ì ë¬¸ë² ì°¨ì´ë ë¤ìê³¼ ê°ìµëë¤.
| generators | async generator | |
|---|---|---|
| ì ì¸ | function* |
async function* |
next()ê° ë°ííë ê° |
{value:â¦, done: true/false} |
{value:â¦, done: true/false}를 ê°ì¸ë Promise |
ì¹ ê°ë°ì íë¤ ë³´ë©´ ëìëì ë¤ì´ì¤ë ë°ì´í° ì¤í¸ë¦¼ì ë¤ë¤ì¼ íë ê²½ì°ê° ì주 ìê¹ëë¤. ì©ëì´ í° íì¼ì ë¤ì´ë¡ëíê±°ë ì ë¡ë í ëì ê°ì´ ë§ì´ì£ .
ì´ë° ë°ì´í°ë¥¼ ì²ë¦¬í ë async ì ëë ì´í°ë¥¼ ì¬ì©í ì ììµëë¤. ì°¸ê³ ë¡ ë¸ë¼ì°ì ë±ì ëªëª í¸ì¤í¸ íê²½ì ë°ì´í° ì¤í¸ë¦¼ì ì²ë¦¬í ì ìê² í´ì£¼ë APIì¸ Streamsì ì ê³µí기ë í©ëë¤. Streams APIìì ì ê³µíë í¹ë³í ì¸í°íì´ì¤ë¥¼ ì¬ì©íë©´, ë°ì´í°ë¥¼ ë³ê²½íì¬ í ì¤í¸ë¦¼ìì ë¤ë¥¸ ì¤í¸ë¦¼ì¼ë¡ ë°ì´í°ë¥¼ ì ë¬í ì ììµëë¤. ë°ë¼ì í쪽ìì ë°ì ë°ì´í°ë¥¼ ë¤ë¥¸ 쪽ì ì¦ê° ì ë¬íë ê² ê°ë¥í´ì§ëë¤.
ëê¸
<code>í그를, ì¬ë¬ ì¤ë¡ 구ì±ë ì½ë를 ì½ì íê³ ì¶ë¤ë©´<pre>í그를 ì´ì©íì¸ì. 10ì¤ ì´ìì ì½ëë plnkr, JSBin, codepen ë±ì ìëë°ì¤ë¥¼ ì¬ì©íì¸ì.