ì½ë°± ì±í°ìì ì¸ê¸í 문ì 를 ë¤ì ì§ì´ë´ ìë¤. ì¤í¬ë¦½í¸ë¥¼ ë¶ë¬ì¤ë ê²ê³¼ ê°ì´ ìì°¨ì ì¼ë¡ ì²ë¦¬í´ì¼ íë ë¹ë기 ìì ì´ ì¬ë¬ ê° ìë¤ê³ ê°ì í´ ë´ ìë¤. ì´ë»ê² í´ì¼ ì´ë° ìí©ì ì½ëë¡ íì´ë¼ ì ììê¹ì?
íë¼ë¯¸ì¤ë¥¼ ì¬ì©íë©´ ì¬ë¬ ê°ì§ í´ê²°ì± ì ë§ë¤ ì ììµëë¤.
ì´ë² ì±í°ìì íë¼ë¯¸ì¤ ì²´ì´ë(promise chaining)ì ì´ì©í ë¹ë기 ì²ë¦¬ì ëí´ ë¤ë£¨ëë¡ íê² ìµëë¤.
íë¼ë¯¸ì¤ ì²´ì´ëì ìëì ê°ì´ ìê²¼ìµëë¤.
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;
});
íë¼ë¯¸ì¤ ì²´ì´ëì resultê° .then í¸ë¤ë¬ì ì²´ì¸(ì¬ì¬)ì íµí´ ì ë¬ëë¤ë ì ìì ì°©ìí ìì´ëì´ì
ëë¤.
ì ììë ìëì ê°ì ììë¡ ì¤íë©ëë¤.
- 1ì´ í ìµì´ íë¼ë¯¸ì¤ê° ì´íë©ëë¤. â
(*) - ì´í 첫ë²ì§¸
.thení¸ë¤ë¬ê° í¸ì¶ë©ëë¤. â(**) - 2ìì ë°íí ê°ì ë¤ì
.thení¸ë¤ë¬ì ì ë¬ë©ëë¤. â(***) - ì´ë° ê³¼ì ì´ ê³ì ì´ì´ì§ëë¤.
resultê° í¸ë¤ë¬ ì²´ì¸ì ë°ë¼ ì ë¬ëë¯ë¡, alert ì°½ì 1, 2, 4ê° ììëë¡ ì¶ë ¥ë©ëë¤.
íë¼ë¯¸ì¤ ì²´ì´ëì´ ê°ë¥í ì´ì ë promise.thenì í¸ì¶íë©´ íë¼ë¯¸ì¤ê° ë°íë기 ë문ì
ëë¤. ë°íë íë¼ë¯¸ì¤ì ë¹ì°í .thenì í¸ì¶í ì ììµëë¤.
íí¸ í¸ë¤ë¬ê° ê°ì ë°íí ëì ì´ ê°ì´ íë¼ë¯¸ì¤ì resultê° ë©ëë¤. ë°ë¼ì ë¤ì .thenì ì´ ê°ì ì´ì©í´ í¸ì¶ë©ëë¤.
ì´ë³´ìë íë¼ë¯¸ì¤ íëì .thenì ì¬ë¬ ê° ì¶ê°í í, ì´ë¥¼ ì²´ì´ëì´ë¼ê³ ì°©ê°íë ê²½ì°ê° ììµëë¤. íì§ë§ ì´ë ì²´ì´ëì´ ìëëë¤.
ìì:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
ììì íë¼ë¯¸ì¤ë íëì¸ë° ì¬ê¸°ì ë±ë¡ë í¸ë¤ë¬ë ì¬ë¬ ê° ì
ëë¤. ì´ í¸ë¤ë¬ë¤ì result를 ìì°¨ì ì¼ë¡ ì ë¬íì§ ìê³ ë
립ì ì¼ë¡ ì²ë¦¬í©ëë¤.
그림ì¼ë¡ íííë©´ ë¤ìê³¼ ê°ìµëë¤. íë¼ë¯¸ì¤ ì²´ì´ëì ë¬ì¬í ì 그림과 ë¹êµí´ ë³´ì¸ì.
ëì¼í íë¼ë¯¸ì¤ì ë±ë¡ë .then 모ëë ëì¼í ê²°ê³¼(íë¼ë¯¸ì¤ì result)를 ë°ìµëë¤. ë°ë¼ì ì ìì를 ì¤ííë©´ ì¼ë¿ ì°½ì ì ë¶ 1ì´ ì¶ë ¥ë©ëë¤.
ì´ë° ìì¼ë¡ í íë¼ë¯¸ì¤ì ì¬ë¬ ê°ì í¸ë¤ë¬ë¥¼ ë±ë¡í´ì ì¬ì©íë ê²½ì°ë ê±°ì ììµëë¤. íë¼ë¯¸ì¤ë ì£¼ë¡ ì²´ì´ëì í´ì ìëë¤.
íë¼ë¯¸ì¤ ë°íí기
.then(handler)ì ì¬ì©ë í¸ë¤ë¬ê° íë¼ë¯¸ì¤ë¥¼ ìì±íê±°ë ë°ííë ê²½ì°ë ììµëë¤.
ì´ ê²½ì° ì´ì´ì§ë í¸ë¤ë¬ë íë¼ë¯¸ì¤ê° ì²ë¦¬ë ëê¹ì§ 기ë¤ë¦¬ë¤ê° ì²ë¦¬ê° ìë£ëë©´ ê·¸ 결과를 ë°ìµëë¤.
ìì:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
ìììì 첫 ë²ì§¸ .thenì 1ì ì¶ë ¥íê³ new Promise(â¦)를 ë°í((*))í©ëë¤.
1ì´ í ì´ íë¼ë¯¸ì¤ê° ì´íëê³ ê·¸ ê²°ê³¼(resolveì ì¸ìì¸ result * 2)ë ë ë²ì§¸ .thenì¼ë¡ ì ë¬ë©ëë¤. ë ë²ì§¸ í¸ë¤ë¬((**))ë 2를 ì¶ë ¥íê³ ëì¼í ê³¼ì ì´ ë°ë³µë©ëë¤.
ë°ë¼ì ì¼ë¿ ì°½ì ì´ì ììì ëì¼íê² 1, 2, 4ê° ì°¨ë¡ëë¡ ì¶ë ¥ë©ëë¤. ë¤ë§ ì¼ë¿ ì°½ ì¬ì´ì 1ì´ì ëë ì´ê° ìê¹ëë¤.
ì´ë ê² í¸ë¤ë¬ ììì íë¼ë¯¸ì¤ë¥¼ ë°ííë ê²ë ë¹ë기 ìì ì²´ì´ëì ê°ë¥íê² í´ì¤ëë¤.
loadScript ìì ê°ì í기
ì§ê¸ê¹ì§ ë°°ì´ ê¸°ë¥ì ì¬ì©í´ ì´ì ì±í°ìì íë¼ë¯¸ì¤ë¥¼ ì¬ì©í´ ì ìí loadScript(ì¤í¬ë¦½í¸ë¥¼ ìì°¨ì ì¼ë¡ ë¶ë¬ì¤)를 ê°ì í´ ë´
ìë¤.
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// ë¶ë¬ì¨ ì¤í¬ë¦½í¸ ìì ì ìë í¨ì를 í¸ì¶í´
// ì¤ì ë¡ ì¤í¬ë¦½í¸ë¤ì´ ì ìì ì¼ë¡ ë¡ëëìëì§ íì¸í©ëë¤.
one();
two();
three();
});
íì´í í¨ì를 ì¬ì©íë©´ ë¤ìê³¼ ê°ì´ ì½ë를 ì¤ì¼ìë ììµëë¤.
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// ì¤í¬ë¦½í¸ë¥¼ ì ìì ì¼ë¡ ë¶ë¬ì기 ë문ì ì¤í¬ë¦½í¸ ë´ì í¨ì를 í¸ì¶í ì ììµëë¤.
one();
two();
three();
});
loadScript를 í¸ì¶í ëë§ë¤ íë¼ë¯¸ì¤ê° ë°íëê³ ë¤ì .thenì ì´ íë¼ë¯¸ì¤ê° ì´íëìì ë ì¤íë©ëë¤. ì´íì ë¤ì ì¤í¬ë¦½í¸ë¥¼ ë¡ë©í기 ìí ì´ê¸°íê° ì§íë©ëë¤. ì¤í¬ë¦½í¸ë ì´ë° ê³¼ì ì ê±°ì³ ìì°¨ì ì¼ë¡ ë¡ëë©ëë¤.
ì²´ì¸ì ë ë§ì ë¹ë기 ëìì ì¶ê°í ìë ìëë°, ì¶ê° ìì ì´ ë§ìì ¸ë ì½ëê° ì¤ë¥¸ìª½ì¼ë¡ 길ì´ì§ì§ ìê³ ìëë¡ë§ ì¦ê°í´ì '멸ë§âì í¼ë¼ë¯¸ëê° ë§ë¤ì´ì§ì§ ììµëë¤.
íí¸, ìëì ê°ì´ ê° loadScriptì .thenì ë°ë¡ ë¶ì¼ ìë ììµëë¤.
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// ì¬ê¸°ì script1, script2, script3ì ì ìë í¨ì를 ì¬ì©í ì ììµëë¤.
one();
two();
three();
});
});
});
ì´ë ê² .thenì ë°ë¡ ë¶ì¬ë ëì¼í ëì(ì¤í¬ë¦½í¸ ì¸ ê°ë¥¼ ìì°¨ì ì¼ë¡ ë¶ë¬ì¤ë ìì
)ì ìíí©ëë¤. íì§ë§ ì½ëê° âì¤ë¥¸ìª½ì¼ë¡â 길ì´ì¡ë¤ì. ì½ë°±ìì ì¸ê¸í 문ì ì ëì¼í 문ì ê° ë°ìíìµëë¤.
íë¼ë¯¸ì¤ë¥¼ ì´ì ë§ ë°°ì°ê¸° ììí´ ì²´ì´ëì ëí´ ì 모른ë¤ë©´ ììê°ì´ ì½ë를 ìì±í ì ììµëë¤. ê·¸ë¬ë ëê° ì²´ì´ëì´ ì í¸ë©ëë¤.
ì¤ì²© í¨ììì ì¸ë¶ ì¤ì½íì ì ê·¼í ì ì기 ë문ì .thenì ë°ë¡ ì°ë ê² ê´ì°®ì ê²½ì°ë ììµëë¤. ì ìì ìì ê°ì¥ ê¹ì ê³³ì ìë ì¤ì²© ì½ë°±ì script1, script2, script3 ìì ìë ë³ì 모ëì ì ê·¼í ì ììµëë¤. ì´ë° ìì¸ ìí©ì´ ìë¤ë ì ëë§ ììëëë¡ í©ìë¤.
í¸ë¤ë¬ë íë¼ë¯¸ì¤ê° ìë thenableì´ë¼ ë¶ë¦¬ë ê°ì²´ë¥¼ ë°íí기ë í©ëë¤. .thenì´ë¼ë ë©ìë를 ê°ì§ ê°ì²´ë 모ë thenableê°ì²´ë¼ê³ ë¶ë¥´ëë°, ì´ ê°ì²´ë íë¼ë¯¸ì¤ì ê°ì ë°©ìì¼ë¡ ì²ë¦¬ë©ëë¤.
âthenableâ ê°ì²´ì ëí ìì´ëì´ë ìëíí° ë¼ì´ë¸ë¬ë¦¬ê° âíë¼ë¯¸ì¤ì í¸í ê°ë¥íâ ìì²´ ê°ì²´ë¥¼ 구íí ì ìë¤ë ì ìì ëììµëë¤. ì´ ê°ì²´ë¤ì ìì²´ íì¥ ë©ìëê° êµ¬íëì´ ìê² ì§ë§ .thenì´ ì기 ë문ì ë¤ì´í°ë¸ íë¼ë¯¸ì¤ìë í¸í ê°ë¥í©ëë¤.
ìëë thenable ê°ì²´ ììì ëë¤.
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { ë¤ì´í°ë¸ ì½ë }
// 1ì´ í this.num*2ì í¨ê» ì´íë¨
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // 1000ë°ë¦¬ ì´ í 2를 ë³´ì¬ì¤
ìë°ì¤í¬ë¦½í¸ë (*)ë¡ íìí ì¤ìì .then í¸ë¤ë¬ê° ë°íí ê°ì²´ë¥¼ íì¸í©ëë¤. ì´ ê°ì²´ì í¸ì¶ ê°ë¥í ë©ìë thenì´ ìì¼ë©´ thenì´ í¸ì¶ë©ëë¤. thenì resolveì rejectë¼ë ë¤ì´í°ë¸ í¨ì를 ì¸ìë¡ ë°ê³ (executorê³¼ ì ì¬í¨), ë ì¤ íëê° í¸ì¶ë ëê¹ì§ 기ë¤ë¦½ëë¤. ì ìììì resolve(2)ë 1ì´ íì í¸ì¶ë©ëë¤((**)). í¸ì¶ í ê²°ê³¼ë ì²´ì¸ì ë°ë¼ ìëë¡ ì ë¬ë©ëë¤.
ì´ë° ìì¼ë¡ 구ííë©´ Promise를 ììë°ì§ ìê³ ë 커ì¤í
ê°ì²´ë¥¼ ì¬ì©í´ íë¼ë¯¸ì¤ ì²´ì´ëì ë§ë¤ ì ììµëë¤.
fetchì ì²´ì´ë í¨ê» ìì©í기
íë¡ í¸ ë¨ìì , ë¤í¸ìí¬ ìì² ì íë¼ë¯¸ì¤ë¥¼ ì주 ì¬ì©í©ëë¤. ì´ì ê´ë ¨ë ìì를 ì´í´ë´ ìë¤.
ìììì ë©ìë fetch를 ì¬ì©í´ ì격 ìë²ìì ì¬ì©ì ì 보를 ê°ì ¸ì¤ê² ìµëë¤. fetchì ë¤ìí ì í 매ê°ë³ìê° ìëë° ìì¸í ë´ì©ì ë³ëì ì±í°ìì ë¤ë£¨ê¸°ë¡ íê³ , ì¬ê¸°ì 기본 문ë²ë§ ì¬ì©í´ ë³´ê² ìµëë¤.
let promise = fetch(url);
ì ì½ë를 ì¤ííë©´ urlì ë¤í¸ìí¬ ìì²ì ë³´ë´ê³ íë¼ë¯¸ì¤ë¥¼ ë°íí©ëë¤. ì격 ìë²ê° í¤ëì í¨ê» ìëµì ë³´ë´ë©´, íë¼ë¯¸ì¤ë response ê°ì²´ì í¨ê» ì´íë©ëë¤. ê·¸ë°ë° ì´ë response ì ì²´ê° ìì í ë¤ì´ë¡ëë기 ì ì íë¼ë¯¸ì¤ë ì´í ìíê° ëì´ë²ë¦½ëë¤.
ìëµì´ ìì í ì¢
ë£ëê³ , ìëµ ì 체를 ì½ì¼ë ¤ë©´ ë©ìë response.text()를 í¸ì¶í´ì¼ í©ëë¤. response.text()ë ì격 ìë²ìì ì ì¡í í
ì¤í¸ ì ì²´ê° ë¤ì´ë¡ëëë©´, ì´ í
ì¤í¸ë¥¼ result ê°ì¼ë¡ ê°ë ì´íë íë¼ë¯¸ì¤ë¥¼ ë°íí©ëë¤.
ìë ì½ë를 ì¤ííë©´ user.jsonì ìì²ì´ ê°ê³ ìë²ìì í´ë¹ í
ì¤í¸ë¥¼ ë¶ë¬ìµëë¤.
fetch('/article/promise-chaining/user.json')
// ì격 ìë²ê° ìëµíë©´ .then ìë ì½ëê° ì¤íë©ëë¤.
.then(function(response) {
// response.text()ë ìëµ í
ì¤í¸ ì ì²´ê° ë¤ì´ë¡ëëë©´
// ìëµ í
ì¤í¸ë¥¼ ìë¡ì´ ì´í íë¼ë¯¸ì¤ë¥¼ ë§ë¤ê³ , ì´ë¥¼ ë°íí©ëë¤.
return response.text();
})
.then(function(text) {
// ì격ìì ë°ìì¨ íì¼ì ë´ì©
alert(text); // {"name": "Violet-Bora-Lee", "isAdmin": true}
});
ê·¸ë°ë° ë©ìë response.json() 를 ì°ë©´ ì격ìì ë°ìì¨ ë°ì´í°ë¥¼ ì½ê³ JSONì¼ë¡ íì±í ì ììµëë¤. ììì ì´ ë©ìëê° ë ì í©íë¯ë¡ 기존ì ìì±í ì½ë를 ì½ê° ë³ê²½í´ ë³´ê² ìµëë¤.
íì´í í¨ìë í¨ê» ì¨ì ì½ë를 ê°ê²°íê² í´ë³´ê² ìµëë¤.
// ì ì½ëì ëì¼í 기ë¥ì íì§ë§, response.json()ì ì격 ìë²ìì ë¶ë¬ì¨ ë´ì©ì JSONì¼ë¡ ë³ê²½í´ì¤ëë¤.
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // Violet-Bora-Lee, ì´ë¦ë§ ì±ê³µì ì¼ë¡ ê°ì ¸ì´
ë¶ë¬ì¨ ì¬ì©ì ì 보를 ê°ì§ê³ 무ì¸ê°ë¥¼ ë í´ë³´ê² ìµëë¤.
GitHubì ìì²ì ë³´ë´ ì¬ì©ì íë¡íì ë¶ë¬ì¤ê³ ìë°í를 ì¶ë ¥í´ ë³´ë ê²ê°ì´ ë§ì´ì£ .
// user.jsonì ìì²ì ë³´ë
ëë¤.
fetch('/article/promise-chaining/user.json')
// ìëµë°ì ë´ì©ì jsonì¼ë¡ ë¶ë¬ìµëë¤.
.then(response => response.json())
// GitHubì ìì²ì ë³´ë
ëë¤.
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// ìëµë°ì ë´ì©ì json ííë¡ ë¶ë¬ìµëë¤.
.then(response => response.json())
// 3ì´ê° ìë°í ì´ë¯¸ì§(githubUser.avatar_url)를 ë³´ì¬ì¤ëë¤.
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
ì½ëë 주ìì ì ì ëë¡ ì ëìí©ëë¤. ê·¸ë°ë° ì ì½ëì íë¼ë¯¸ì¤ë¥¼ ë¤ë£¨ëë° ìí° ê°ë°ìê° ì주 ì ì§ë¥´ë ì ì¬ì 문ì ê° ë´ì¬ë¼ ììµëë¤.
(*)ë¡ íìí ì¤ì ë´
ìë¤. ë§ì½ ìë°íê° ì ê¹ ë³´ìë¤ê° ì¬ë¼ì§ ì´íì 무ì¸ê°ë¥¼ íê³ ì¶ì¼ë©´ ì´ë»ê² í´ì¼ í ê¹ì? ì¬ì©ì ì 보를 ìì í ì ìê² í´ì£¼ë í¼ì ë³´ì¬ì£¼ë ê² ê°ì ìì
ì ì¶ê°íë ê²½ì°ê°ì´ ë§ì´ì£ . ì§ê¸ì¼ë¡ì ë°©ë²ì´ ììµëë¤.
ì²´ì¸ì íì¥í ì ìëë¡ ë§ë¤ë ¤ë©´ ìë°íê° ì¬ë¼ì§ ë ì´í íë¼ë¯¸ì¤ë¥¼ ë°íí´ ì¤ì¼ í©ëë¤.
ìëì ê°ì´ ë§ì´ì£ .
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) { // (*)
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser); // (**)
}, 3000);
}))
// 3ì´ í ëìí¨
.then(githubUser => alert(`${githubUser.name}ì ì´ë¯¸ì§ë¥¼ ì±ê³µì ì¼ë¡ ì¶ë ¥íììµëë¤.`));
(*)ë¡ íìí ê³³ì .then í¸ë¤ë¬ë ì´ì setTimeoutìì resolve(githubUser)를 í¸ì¶íì ë((**)) ë§ ì²ë¦¬ìíê° ëë new Promise를 ë°íí©ëë¤. ì²´ì¸ì ë¤ì .thenì ì´ë¥¼ 기ë¤ë¦½ëë¤.
ë¹ë기 ëìì íì íë¼ë¯¸ì¤ë¥¼ ë°ííëë¡ íë ê²ì´ ì¢ìµëë¤. ì§ê¸ì ì²´ì¸ì íì¥í ê³íì´ ìëë¼ë ì´ë ê² êµ¬íí´ ëì¼ë©´ ëì¤ì ì²´ì¸ íì¥ì´ íìí ê²½ì° ìì½ê² ì²´ì¸ì íì¥í ì ììµëë¤.
ì´ì ì½ë를 ì¬ì¬ì© ê°ë¥í í¨ì ë¨ìë¡ ë¶ë¦¬í´ ë§ë¬´ë¦¬íê² ìµëë¤.
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// í¨ì를 ì´ì©íì¬ ë¤ì ëì¼ ìì
ìí
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
ìì½
.then ëë .catch, .finallyì í¸ë¤ë¬(ì´ë¤ ê²½ì°ë ìê´ìì)ê° íë¼ë¯¸ì¤ë¥¼ ë°ííë©´, ëë¨¸ì§ ì²´ì¸ì íë¼ë¯¸ì¤ê° ì²ë¦¬ë ëê¹ì§ ë기í©ëë¤. ì²ë¦¬ê° ìë£ëë©´ íë¼ë¯¸ì¤ì result(ê° ëë ìë¬)ê° ë¤ì ì²´ì¸ì¼ë¡ ì ë¬ë©ëë¤.
ì´ë¥¼ 그림ì¼ë¡ ëíë´ë©´ ìëì ê°ìµëë¤.
ëê¸
<code>í그를, ì¬ë¬ ì¤ë¡ 구ì±ë ì½ë를 ì½ì íê³ ì¶ë¤ë©´<pre>í그를 ì´ì©íì¸ì. 10ì¤ ì´ìì ì½ëë plnkr, JSBin, codepen ë±ì ìëë°ì¤ë¥¼ ì¬ì©íì¸ì.