[{"data":1,"prerenderedAt":3673},["ShallowReactive",2],{"blog-post-\u002Fko\u002Fjavascript-async-error-handling":3,"blog-related-source":3324},{"id":4,"title":5,"body":6,"description":3312,"extension":3313,"meta":3314,"navigation":85,"path":3319,"seo":3320,"sitemap":3321,"stem":3322,"__hash__":3323},"ko\u002Fko\u002Fjavascript-async-error-handling.md","자바스크립트 비동기 에러 올바르게 처리하기: try\u002Fcatch가 못 잡는 경우부터 Promise.all vs allSettled까지",{"type":7,"value":8,"toc":3285},"minimark",[9,14,23,157,165,185,236,255,258,261,273,284,440,465,467,475,487,569,572,678,689,691,699,709,786,789,2343,2357,2362,2416,2418,2424,2434,2540,2552,2560,2642,2650,2671,2673,2677,2686,2773,2784,2929,2932,2934,2938,2945,2951,3014,3018,3052,3061,3068,3071,3115,3126,3128,3132,3259,3281],[10,11,13],"h2",{"id":12},"trycatch로-감쌌는데-왜-에러가-새어-나갈까","try\u002Fcatch로 감쌌는데 왜 에러가 새어 나갈까?",[15,16,17,18,22],"p",{},"데이터를 불러오는 함수를 ",[19,20,21],"code",{},"try\u002Fcatch","로 감쌌다. 그런데 에러가 잡히지 않는다.",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-js shiki shiki-themes github-light github-dark","async function load() {\n  throw new Error(\"서버 에러\");\n}\n\ntry {\n  load();\n  console.log(\"try 블록 통과\");\n} catch (e) {\n  console.log(\"잡힘\", e.message); \u002F\u002F 실행되지 않는다\n}\n","js","",[19,31,32,52,74,80,87,96,105,121,133,152],{"__ignoreMap":29},[33,34,37,41,44,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","async",[33,42,43],{"class":39}," function",[33,45,47],{"class":46},"sScJk"," load",[33,49,51],{"class":50},"sVt8B","() {\n",[33,53,55,58,61,64,67,71],{"class":35,"line":54},2,[33,56,57],{"class":39},"  throw",[33,59,60],{"class":39}," new",[33,62,63],{"class":46}," Error",[33,65,66],{"class":50},"(",[33,68,70],{"class":69},"sZZnC","\"서버 에러\"",[33,72,73],{"class":50},");\n",[33,75,77],{"class":35,"line":76},3,[33,78,79],{"class":50},"}\n",[33,81,83],{"class":35,"line":82},4,[33,84,86],{"emptyLinePlaceholder":85},true,"\n",[33,88,90,93],{"class":35,"line":89},5,[33,91,92],{"class":39},"try",[33,94,95],{"class":50}," {\n",[33,97,99,102],{"class":35,"line":98},6,[33,100,101],{"class":46},"  load",[33,103,104],{"class":50},"();\n",[33,106,108,111,114,116,119],{"class":35,"line":107},7,[33,109,110],{"class":50},"  console.",[33,112,113],{"class":46},"log",[33,115,66],{"class":50},[33,117,118],{"class":69},"\"try 블록 통과\"",[33,120,73],{"class":50},[33,122,124,127,130],{"class":35,"line":123},8,[33,125,126],{"class":50},"} ",[33,128,129],{"class":39},"catch",[33,131,132],{"class":50}," (e) {\n",[33,134,136,138,140,142,145,148],{"class":35,"line":135},9,[33,137,110],{"class":50},[33,139,113],{"class":46},[33,141,66],{"class":50},[33,143,144],{"class":69},"\"잡힘\"",[33,146,147],{"class":50},", e.message); ",[33,149,151],{"class":150},"sJ8bj","\u002F\u002F 실행되지 않는다\n",[33,153,155],{"class":35,"line":154},10,[33,156,79],{"class":50},[24,158,163],{"className":159,"code":161,"language":162},[160],"language-text","try 블록 통과\nUncaught (in promise) Error: 서버 에러\n","text",[19,164,161],{"__ignoreMap":29},[15,166,167,169,170,174,175,178,179,181,182,184],{},[19,168,40],{}," 함수는 에러를 ",[171,172,173],"strong",{},"던지지 않고",", 실패한(rejected) Promise를 ",[171,176,177],{},"돌려준다."," ",[19,180,92],{}," 블록은 Promise를 받자마자 끝나버리고, 에러는 나중에 Promise 안에서 터지기 때문에 ",[19,183,129],{},"가 잡을 기회가 없다.",[24,186,188],{"className":26,"code":187,"language":28,"meta":29,"style":29},"try {\n  await load(); \u002F\u002F ✅ await가 있어야 rejected Promise가 에러로 바뀌어 catch로 온다\n} catch (e) {\n  console.log(\"잡힘\", e.message); \u002F\u002F \"잡힘 서버 에러\"\n}\n",[19,189,190,196,209,217,232],{"__ignoreMap":29},[33,191,192,194],{"class":35,"line":36},[33,193,92],{"class":39},[33,195,95],{"class":50},[33,197,198,201,203,206],{"class":35,"line":54},[33,199,200],{"class":39},"  await",[33,202,47],{"class":46},[33,204,205],{"class":50},"(); ",[33,207,208],{"class":150},"\u002F\u002F ✅ await가 있어야 rejected Promise가 에러로 바뀌어 catch로 온다\n",[33,210,211,213,215],{"class":35,"line":76},[33,212,126],{"class":50},[33,214,129],{"class":39},[33,216,132],{"class":50},[33,218,219,221,223,225,227,229],{"class":35,"line":82},[33,220,110],{"class":50},[33,222,113],{"class":46},[33,224,66],{"class":50},[33,226,144],{"class":69},[33,228,147],{"class":50},[33,230,231],{"class":150},"\u002F\u002F \"잡힘 서버 에러\"\n",[33,233,234],{"class":35,"line":89},[33,235,79],{"class":50},[237,238,239],"blockquote",{},[15,240,241,242,244,245,178,252,254],{},"비동기 에러를 ",[19,243,21],{},"로 잡으려면 ",[171,246,247,248,251],{},"반드시 ",[19,249,250],{},"await","가 있어야 한다.",[19,253,250],{},"가 rejected Promise를 \"던져진 에러\"로 바꿔주는 다리 역할을 한다.",[15,256,257],{},"비동기 코드의 에러 처리는 대부분 이런 \"있는 줄 알았는데 없는\" 함정에서 무너진다. 하나씩 보자.",[259,260],"hr",{},[10,262,264,265,268,269,272],{"id":263},"함정-1-return과-return-await는-다르다","함정 1. ",[19,266,267],{},"return","과 ",[19,270,271],{},"return await","는 다르다",[15,274,275,277,278,280,281,283],{},[19,276,92],{}," 안에서 Promise를 그냥 ",[19,279,267],{},"하면, 그 Promise가 실패해도 이 함수의 ",[19,282,129],{},"는 실행되지 않는다.",[24,285,287],{"className":26,"code":286,"language":28,"meta":29,"style":29},"async function a1() {\n  try {\n    return load(); \u002F\u002F ❌ Promise를 그대로 넘기고 함수는 끝남\n  } catch (e) {\n    return \"대체 값\";\n  }\n}\n\nasync function a2() {\n  try {\n    return await load(); \u002F\u002F ✅ 여기서 기다리므로 실패하면 catch로 온다\n  } catch (e) {\n    return \"대체 값\";\n  }\n}\n\nawait a1(); \u002F\u002F Error: 서버 에러 (catch가 무시됨)\nawait a2(); \u002F\u002F \"대체 값\"\n",[19,288,289,300,307,319,328,338,343,347,351,362,368,383,392,401,406,411,416,428],{"__ignoreMap":29},[33,290,291,293,295,298],{"class":35,"line":36},[33,292,40],{"class":39},[33,294,43],{"class":39},[33,296,297],{"class":46}," a1",[33,299,51],{"class":50},[33,301,302,305],{"class":35,"line":54},[33,303,304],{"class":39},"  try",[33,306,95],{"class":50},[33,308,309,312,314,316],{"class":35,"line":76},[33,310,311],{"class":39},"    return",[33,313,47],{"class":46},[33,315,205],{"class":50},[33,317,318],{"class":150},"\u002F\u002F ❌ Promise를 그대로 넘기고 함수는 끝남\n",[33,320,321,324,326],{"class":35,"line":82},[33,322,323],{"class":50},"  } ",[33,325,129],{"class":39},[33,327,132],{"class":50},[33,329,330,332,335],{"class":35,"line":89},[33,331,311],{"class":39},[33,333,334],{"class":69}," \"대체 값\"",[33,336,337],{"class":50},";\n",[33,339,340],{"class":35,"line":98},[33,341,342],{"class":50},"  }\n",[33,344,345],{"class":35,"line":107},[33,346,79],{"class":50},[33,348,349],{"class":35,"line":123},[33,350,86],{"emptyLinePlaceholder":85},[33,352,353,355,357,360],{"class":35,"line":135},[33,354,40],{"class":39},[33,356,43],{"class":39},[33,358,359],{"class":46}," a2",[33,361,51],{"class":50},[33,363,364,366],{"class":35,"line":154},[33,365,304],{"class":39},[33,367,95],{"class":50},[33,369,371,373,376,378,380],{"class":35,"line":370},11,[33,372,311],{"class":39},[33,374,375],{"class":39}," await",[33,377,47],{"class":46},[33,379,205],{"class":50},[33,381,382],{"class":150},"\u002F\u002F ✅ 여기서 기다리므로 실패하면 catch로 온다\n",[33,384,386,388,390],{"class":35,"line":385},12,[33,387,323],{"class":50},[33,389,129],{"class":39},[33,391,132],{"class":50},[33,393,395,397,399],{"class":35,"line":394},13,[33,396,311],{"class":39},[33,398,334],{"class":69},[33,400,337],{"class":50},[33,402,404],{"class":35,"line":403},14,[33,405,342],{"class":50},[33,407,409],{"class":35,"line":408},15,[33,410,79],{"class":50},[33,412,414],{"class":35,"line":413},16,[33,415,86],{"emptyLinePlaceholder":85},[33,417,419,421,423,425],{"class":35,"line":418},17,[33,420,250],{"class":39},[33,422,297],{"class":46},[33,424,205],{"class":50},[33,426,427],{"class":150},"\u002F\u002F Error: 서버 에러 (catch가 무시됨)\n",[33,429,431,433,435,437],{"class":35,"line":430},18,[33,432,250],{"class":39},[33,434,359],{"class":46},[33,436,205],{"class":50},[33,438,439],{"class":150},"\u002F\u002F \"대체 값\"\n",[441,442,443],"tip",{},[237,444,445],{},[15,446,447,449,450,453,454,456,457,459,460,268,462,464],{},[19,448,92],{}," 블록 ",[171,451,452],{},"안","에서 Promise를 반환할 때는 ",[19,455,271],{},"를 쓰자. ",[19,458,92],{}," 밖이라면 ",[19,461,267],{},[19,463,271],{},"의 결과는 같다.",[259,466],{},[10,468,470,471,474],{"id":469},"함정-2-fetch는-404에서도-성공한다","함정 2. ",[19,472,473],{},"fetch","는 404에서도 성공한다",[15,476,477,479,480,483,484,486],{},[19,478,473],{},"가 실패(reject)하는 건 ",[171,481,482],{},"네트워크 자체가 안 될 때뿐","이다. 서버가 404나 500을 돌려줘도 ",[19,485,473],{},"는 \"응답을 잘 받았다\"며 성공으로 처리한다.",[488,489,490,510],"table",{},[491,492,493],"thead",{},[494,495,496,500,505],"tr",{},[497,498,499],"th",{},"상황",[497,501,502,504],{},[19,503,473],{}," 결과",[497,506,507],{},[19,508,509],{},"res.ok",[511,512,513,527,541,554],"tbody",{},[494,514,515,519,522],{},[516,517,518],"td",{},"200 OK",[516,520,521],{},"성공",[516,523,524],{},[19,525,526],{},"true",[494,528,529,532,536],{},[516,530,531],{},"404 Not Found",[516,533,534],{},[171,535,521],{},[516,537,538],{},[19,539,540],{},"false",[494,542,543,546,550],{},[516,544,545],{},"500 Server Error",[516,547,548],{},[171,549,521],{},[516,551,552],{},[19,553,540],{},[494,555,556,559,566],{},[516,557,558],{},"인터넷 끊김, 잘못된 주소, CORS 차단",[516,560,561,562,565],{},"실패 (",[19,563,564],{},"TypeError",")",[516,567,568],{},"-",[15,570,571],{},"그래서 상태 코드는 직접 확인해야 한다.",[24,573,575],{"className":26,"code":574,"language":28,"meta":29,"style":29},"async function getJSON(url) {\n  const res = await fetch(url);\n  if (!res.ok) {\n    throw new Error(`요청 실패: ${res.status}`);\n  }\n  return res.json();\n}\n",[19,576,577,595,615,629,657,661,674],{"__ignoreMap":29},[33,578,579,581,583,586,588,592],{"class":35,"line":36},[33,580,40],{"class":39},[33,582,43],{"class":39},[33,584,585],{"class":46}," getJSON",[33,587,66],{"class":50},[33,589,591],{"class":590},"s4XuR","url",[33,593,594],{"class":50},") {\n",[33,596,597,600,604,607,609,612],{"class":35,"line":54},[33,598,599],{"class":39},"  const",[33,601,603],{"class":602},"sj4cs"," res",[33,605,606],{"class":39}," =",[33,608,375],{"class":39},[33,610,611],{"class":46}," fetch",[33,613,614],{"class":50},"(url);\n",[33,616,617,620,623,626],{"class":35,"line":76},[33,618,619],{"class":39},"  if",[33,621,622],{"class":50}," (",[33,624,625],{"class":39},"!",[33,627,628],{"class":50},"res.ok) {\n",[33,630,631,634,636,638,640,643,646,649,652,655],{"class":35,"line":82},[33,632,633],{"class":39},"    throw",[33,635,60],{"class":39},[33,637,63],{"class":46},[33,639,66],{"class":50},[33,641,642],{"class":69},"`요청 실패: ${",[33,644,645],{"class":50},"res",[33,647,648],{"class":69},".",[33,650,651],{"class":50},"status",[33,653,654],{"class":69},"}`",[33,656,73],{"class":50},[33,658,659],{"class":35,"line":89},[33,660,342],{"class":50},[33,662,663,666,669,672],{"class":35,"line":98},[33,664,665],{"class":39},"  return",[33,667,668],{"class":50}," res.",[33,670,671],{"class":46},"json",[33,673,104],{"class":50},[33,675,676],{"class":35,"line":107},[33,677,79],{"class":50},[15,679,680,681,684,685,688],{},"이 확인이 빠지면 404 페이지의 HTML을 ",[19,682,683],{},"res.json()","으로 읽다가 엉뚱한 ",[19,686,687],{},"SyntaxError","가 나서, 진짜 원인(404)을 찾기 어려워진다.",[259,690],{},[10,692,694,695,698],{"id":693},"함정-3-promiseall은-하나만-실패해도-전부-실패한다","함정 3. ",[19,696,697],{},"Promise.all","은 하나만 실패해도 전부 실패한다",[15,700,701,702,704,705,708],{},"여러 요청을 동시에 보낼 때 쓰는 ",[19,703,697],{},"은 ",[171,706,707],{},"하나라도 실패하면 즉시 실패","한다. 나머지가 성공했어도 그 결과는 받을 수 없다.",[488,710,711,724],{},[491,712,713],{},[494,714,715,718,721],{},[497,716,717],{},"메서드",[497,719,720],{},"언제 끝나나",[497,722,723],{},"결과",[511,725,726,741,760,773],{},[494,727,728,732,738],{},[516,729,730],{},[19,731,697],{},[516,733,734,735],{},"모두 성공하거나, ",[171,736,737],{},"하나라도 실패하면 즉시",[516,739,740],{},"성공 값 배열 또는 첫 에러",[494,742,743,748,754],{},[516,744,745],{},[19,746,747],{},"Promise.allSettled",[516,749,750,753],{},[171,751,752],{},"모두 끝날 때까지"," 기다림",[516,755,756,757],{},"각각의 ",[19,758,759],{},"{ status, value \u002F reason }",[494,761,762,767,770],{},[516,763,764],{},[19,765,766],{},"Promise.any",[516,768,769],{},"하나라도 성공하면",[516,771,772],{},"첫 성공 값 (전부 실패하면 에러)",[494,774,775,780,783],{},[516,776,777],{},[19,778,779],{},"Promise.race",[516,781,782],{},"하나라도 끝나면 (성공이든 실패든)",[516,784,785],{},"가장 먼저 끝난 결과",[15,787,788],{},"아래 미리보기에서 버튼을 눌러보자. A(0.6초), B(0.3초, 실패), C(0.9초) 세 요청을 동시에 보낸다.",[790,791,792,945,1235],"code-group",{},[24,793,797],{"className":794,"code":795,"language":796,"meta":29,"style":29},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"buttons\">\n  \u003Cbutton data-run=\"all\">Promise.all\u003C\u002Fbutton>\n  \u003Cbutton data-run=\"allSettled\">Promise.allSettled\u003C\u002Fbutton>\n  \u003Cbutton data-run=\"sequential\">for...of + await (순차)\u003C\u002Fbutton>\n  \u003Cbutton data-run=\"parallel\">map + Promise.all (병렬)\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003Col id=\"log\">\u003Cli>버튼을 눌러보세요\u003C\u002Fli>\u003C\u002Fol>\n","html",[19,798,799,820,843,863,883,903,912],{"__ignoreMap":29},[33,800,801,804,808,811,814,817],{"class":35,"line":36},[33,802,803],{"class":50},"\u003C",[33,805,807],{"class":806},"s9eBZ","div",[33,809,810],{"class":46}," class",[33,812,813],{"class":50},"=",[33,815,816],{"class":69},"\"buttons\"",[33,818,819],{"class":50},">\n",[33,821,822,825,828,831,833,836,839,841],{"class":35,"line":54},[33,823,824],{"class":50},"  \u003C",[33,826,827],{"class":806},"button",[33,829,830],{"class":46}," data-run",[33,832,813],{"class":50},[33,834,835],{"class":69},"\"all\"",[33,837,838],{"class":50},">Promise.all\u003C\u002F",[33,840,827],{"class":806},[33,842,819],{"class":50},[33,844,845,847,849,851,853,856,859,861],{"class":35,"line":76},[33,846,824],{"class":50},[33,848,827],{"class":806},[33,850,830],{"class":46},[33,852,813],{"class":50},[33,854,855],{"class":69},"\"allSettled\"",[33,857,858],{"class":50},">Promise.allSettled\u003C\u002F",[33,860,827],{"class":806},[33,862,819],{"class":50},[33,864,865,867,869,871,873,876,879,881],{"class":35,"line":82},[33,866,824],{"class":50},[33,868,827],{"class":806},[33,870,830],{"class":46},[33,872,813],{"class":50},[33,874,875],{"class":69},"\"sequential\"",[33,877,878],{"class":50},">for...of + await (순차)\u003C\u002F",[33,880,827],{"class":806},[33,882,819],{"class":50},[33,884,885,887,889,891,893,896,899,901],{"class":35,"line":89},[33,886,824],{"class":50},[33,888,827],{"class":806},[33,890,830],{"class":46},[33,892,813],{"class":50},[33,894,895],{"class":69},"\"parallel\"",[33,897,898],{"class":50},">map + Promise.all (병렬)\u003C\u002F",[33,900,827],{"class":806},[33,902,819],{"class":50},[33,904,905,908,910],{"class":35,"line":98},[33,906,907],{"class":50},"\u003C\u002F",[33,909,807],{"class":806},[33,911,819],{"class":50},[33,913,914,916,919,922,924,927,930,933,936,938,941,943],{"class":35,"line":107},[33,915,803],{"class":50},[33,917,918],{"class":806},"ol",[33,920,921],{"class":46}," id",[33,923,813],{"class":50},[33,925,926],{"class":69},"\"log\"",[33,928,929],{"class":50},">\u003C",[33,931,932],{"class":806},"li",[33,934,935],{"class":50},">버튼을 눌러보세요\u003C\u002F",[33,937,932],{"class":806},[33,939,940],{"class":50},">\u003C\u002F",[33,942,918],{"class":806},[33,944,819],{"class":50},[24,946,950],{"className":947,"code":948,"language":949,"meta":29,"style":29},"language-css shiki shiki-themes github-light github-dark",".buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }\nbutton { font: inherit; font-size: 14px; padding: 6px 10px; border: 1px solid #ccc; border-radius: 8px; background: #fff; cursor: pointer; }\n#log { font: 13px\u002F1.7 ui-monospace, monospace; padding-left: 0; list-style: none; margin: 0; min-height: 190px; }\n.ok { color: #15803d; }\n.fail { color: #dc2626; }\n.result { font-weight: 700; color: #b45309; }\n","css",[19,951,952,1008,1102,1176,1193,1209],{"__ignoreMap":29},[33,953,954,957,960,963,966,969,972,975,977,980,982,985,987,990,993,995,998,1000,1003,1005],{"class":35,"line":36},[33,955,956],{"class":46},".buttons",[33,958,959],{"class":50}," { ",[33,961,962],{"class":602},"display",[33,964,965],{"class":50},": ",[33,967,968],{"class":602},"flex",[33,970,971],{"class":50},"; ",[33,973,974],{"class":602},"flex-wrap",[33,976,965],{"class":50},[33,978,979],{"class":602},"wrap",[33,981,971],{"class":50},[33,983,984],{"class":602},"gap",[33,986,965],{"class":50},[33,988,989],{"class":602},"6",[33,991,992],{"class":39},"px",[33,994,971],{"class":50},[33,996,997],{"class":602},"margin-bottom",[33,999,965],{"class":50},[33,1001,1002],{"class":602},"12",[33,1004,992],{"class":39},[33,1006,1007],{"class":50},"; }\n",[33,1009,1010,1012,1014,1017,1019,1022,1024,1027,1029,1032,1034,1036,1039,1041,1043,1045,1048,1050,1052,1055,1057,1060,1062,1065,1068,1070,1073,1075,1078,1080,1082,1085,1087,1090,1092,1095,1097,1100],{"class":35,"line":54},[33,1011,827],{"class":806},[33,1013,959],{"class":50},[33,1015,1016],{"class":602},"font",[33,1018,965],{"class":50},[33,1020,1021],{"class":602},"inherit",[33,1023,971],{"class":50},[33,1025,1026],{"class":602},"font-size",[33,1028,965],{"class":50},[33,1030,1031],{"class":602},"14",[33,1033,992],{"class":39},[33,1035,971],{"class":50},[33,1037,1038],{"class":602},"padding",[33,1040,965],{"class":50},[33,1042,989],{"class":602},[33,1044,992],{"class":39},[33,1046,1047],{"class":602}," 10",[33,1049,992],{"class":39},[33,1051,971],{"class":50},[33,1053,1054],{"class":602},"border",[33,1056,965],{"class":50},[33,1058,1059],{"class":602},"1",[33,1061,992],{"class":39},[33,1063,1064],{"class":602}," solid",[33,1066,1067],{"class":602}," #ccc",[33,1069,971],{"class":50},[33,1071,1072],{"class":602},"border-radius",[33,1074,965],{"class":50},[33,1076,1077],{"class":602},"8",[33,1079,992],{"class":39},[33,1081,971],{"class":50},[33,1083,1084],{"class":602},"background",[33,1086,965],{"class":50},[33,1088,1089],{"class":602},"#fff",[33,1091,971],{"class":50},[33,1093,1094],{"class":602},"cursor",[33,1096,965],{"class":50},[33,1098,1099],{"class":602},"pointer",[33,1101,1007],{"class":50},[33,1103,1104,1107,1109,1111,1113,1116,1118,1121,1124,1127,1130,1133,1135,1138,1140,1143,1145,1148,1150,1153,1155,1158,1160,1162,1164,1167,1169,1172,1174],{"class":35,"line":76},[33,1105,1106],{"class":46},"#log",[33,1108,959],{"class":50},[33,1110,1016],{"class":602},[33,1112,965],{"class":50},[33,1114,1115],{"class":602},"13",[33,1117,992],{"class":39},[33,1119,1120],{"class":50},"\u002F",[33,1122,1123],{"class":602},"1.7",[33,1125,1126],{"class":602}," ui-monospace",[33,1128,1129],{"class":50},", ",[33,1131,1132],{"class":602},"monospace",[33,1134,971],{"class":50},[33,1136,1137],{"class":602},"padding-left",[33,1139,965],{"class":50},[33,1141,1142],{"class":602},"0",[33,1144,971],{"class":50},[33,1146,1147],{"class":602},"list-style",[33,1149,965],{"class":50},[33,1151,1152],{"class":602},"none",[33,1154,971],{"class":50},[33,1156,1157],{"class":602},"margin",[33,1159,965],{"class":50},[33,1161,1142],{"class":602},[33,1163,971],{"class":50},[33,1165,1166],{"class":602},"min-height",[33,1168,965],{"class":50},[33,1170,1171],{"class":602},"190",[33,1173,992],{"class":39},[33,1175,1007],{"class":50},[33,1177,1178,1181,1183,1186,1188,1191],{"class":35,"line":82},[33,1179,1180],{"class":46},".ok",[33,1182,959],{"class":50},[33,1184,1185],{"class":602},"color",[33,1187,965],{"class":50},[33,1189,1190],{"class":602},"#15803d",[33,1192,1007],{"class":50},[33,1194,1195,1198,1200,1202,1204,1207],{"class":35,"line":89},[33,1196,1197],{"class":46},".fail",[33,1199,959],{"class":50},[33,1201,1185],{"class":602},[33,1203,965],{"class":50},[33,1205,1206],{"class":602},"#dc2626",[33,1208,1007],{"class":50},[33,1210,1211,1214,1216,1219,1221,1224,1226,1228,1230,1233],{"class":35,"line":98},[33,1212,1213],{"class":46},".result",[33,1215,959],{"class":50},[33,1217,1218],{"class":602},"font-weight",[33,1220,965],{"class":50},[33,1222,1223],{"class":602},"700",[33,1225,971],{"class":50},[33,1227,1185],{"class":602},[33,1229,965],{"class":50},[33,1231,1232],{"class":602},"#b45309",[33,1234,1007],{"class":50},[24,1236,1238],{"className":26,"code":1237,"language":28,"meta":29,"style":29},"const logEl = document.querySelector(\"#log\");\nlet t0 = 0;\nlet runId = 0;\n\nfunction line(msg, cls = \"\", id) {\n  if (id !== runId) return; \u002F\u002F 이전 실행의 늦은 로그는 무시\n  const li = document.createElement(\"li\");\n  li.className = cls;\n  li.textContent = `[${Math.round(performance.now() - t0)}ms] ${msg}`;\n  logEl.append(li);\n}\n\nfunction request(name, ms, fail, id) {\n  line(`${name} 요청 시작`, \"\", id);\n  return new Promise((resolve, reject) =>\n    setTimeout(() => {\n      if (fail) {\n        line(`${name} 실패 ✗`, \"fail\", id);\n        reject(new Error(`${name} 실패`));\n      } else {\n        line(`${name} 완료 ✓`, \"ok\", id);\n        resolve(name);\n      }\n    }, ms)\n  );\n}\n\nconst scenarios = {\n  async all(id) {\n    try {\n      const r = await Promise.all([request(\"A\", 600, false, id), request(\"B\", 300, true, id), request(\"C\", 900, false, id)]);\n      line(`결과: ${r}`, \"result\", id);\n    } catch (e) {\n      line(`catch: ${e.message} → A, C 결과는 못 받음 (요청은 계속 진행됨)`, \"result\", id);\n    }\n  },\n  async allSettled(id) {\n    const r = await Promise.allSettled([request(\"A\", 600, false, id), request(\"B\", 300, true, id), request(\"C\", 900, false, id)]);\n    line(`결과: ${r.map((x) => (x.status === \"fulfilled\" ? x.value + \" 성공\" : x.reason.message)).join(\", \")}`, \"result\", id);\n  },\n  async sequential(id) {\n    const out = [];\n    for (const name of [\"A\", \"B\", \"C\"]) out.push(await request(name, 400, false, id));\n    line(`결과: ${out} (하나씩 기다려서 느림)`, \"result\", id);\n  },\n  async parallel(id) {\n    const out = await Promise.all([\"A\", \"B\", \"C\"].map((name) => request(name, 400, false, id)));\n    line(`결과: ${out} (동시에 보내서 빠름)`, \"result\", id);\n  },\n};\n\ndocument.querySelectorAll(\"[data-run]\").forEach((btn) =>\n  btn.addEventListener(\"click\", () => {\n    const id = ++runId;\n    logEl.innerHTML = \"\";\n    t0 = performance.now();\n    scenarios[btn.dataset.run](id);\n  })\n);\n",[19,1239,1240,1263,1278,1291,1295,1325,1345,1366,1376,1423,1434,1438,1442,1470,1493,1519,1532,1540,1561,1586,1597,1618,1627,1633,1639,1645,1650,1655,1667,1682,1690,1770,1793,1803,1830,1836,1842,1856,1925,2017,2022,2036,2049,2103,2124,2129,2143,2198,2218,2223,2229,2234,2263,2284,2299,2311,2326,2332,2338],{"__ignoreMap":29},[33,1241,1242,1245,1248,1250,1253,1256,1258,1261],{"class":35,"line":36},[33,1243,1244],{"class":39},"const",[33,1246,1247],{"class":602}," logEl",[33,1249,606],{"class":39},[33,1251,1252],{"class":50}," document.",[33,1254,1255],{"class":46},"querySelector",[33,1257,66],{"class":50},[33,1259,1260],{"class":69},"\"#log\"",[33,1262,73],{"class":50},[33,1264,1265,1268,1271,1273,1276],{"class":35,"line":54},[33,1266,1267],{"class":39},"let",[33,1269,1270],{"class":50}," t0 ",[33,1272,813],{"class":39},[33,1274,1275],{"class":602}," 0",[33,1277,337],{"class":50},[33,1279,1280,1282,1285,1287,1289],{"class":35,"line":76},[33,1281,1267],{"class":39},[33,1283,1284],{"class":50}," runId ",[33,1286,813],{"class":39},[33,1288,1275],{"class":602},[33,1290,337],{"class":50},[33,1292,1293],{"class":35,"line":82},[33,1294,86],{"emptyLinePlaceholder":85},[33,1296,1297,1300,1303,1305,1308,1310,1313,1315,1318,1320,1323],{"class":35,"line":89},[33,1298,1299],{"class":39},"function",[33,1301,1302],{"class":46}," line",[33,1304,66],{"class":50},[33,1306,1307],{"class":590},"msg",[33,1309,1129],{"class":50},[33,1311,1312],{"class":590},"cls",[33,1314,606],{"class":39},[33,1316,1317],{"class":69}," \"\"",[33,1319,1129],{"class":50},[33,1321,1322],{"class":590},"id",[33,1324,594],{"class":50},[33,1326,1327,1329,1332,1335,1338,1340,1342],{"class":35,"line":98},[33,1328,619],{"class":39},[33,1330,1331],{"class":50}," (id ",[33,1333,1334],{"class":39},"!==",[33,1336,1337],{"class":50}," runId) ",[33,1339,267],{"class":39},[33,1341,971],{"class":50},[33,1343,1344],{"class":150},"\u002F\u002F 이전 실행의 늦은 로그는 무시\n",[33,1346,1347,1349,1352,1354,1356,1359,1361,1364],{"class":35,"line":107},[33,1348,599],{"class":39},[33,1350,1351],{"class":602}," li",[33,1353,606],{"class":39},[33,1355,1252],{"class":50},[33,1357,1358],{"class":46},"createElement",[33,1360,66],{"class":50},[33,1362,1363],{"class":69},"\"li\"",[33,1365,73],{"class":50},[33,1367,1368,1371,1373],{"class":35,"line":123},[33,1369,1370],{"class":50},"  li.className ",[33,1372,813],{"class":39},[33,1374,1375],{"class":50}," cls;\n",[33,1377,1378,1381,1383,1386,1389,1391,1394,1396,1399,1401,1404,1407,1409,1412,1414,1417,1419,1421],{"class":35,"line":135},[33,1379,1380],{"class":50},"  li.textContent ",[33,1382,813],{"class":39},[33,1384,1385],{"class":69}," `[${",[33,1387,1388],{"class":50},"Math",[33,1390,648],{"class":69},[33,1392,1393],{"class":46},"round",[33,1395,66],{"class":69},[33,1397,1398],{"class":50},"performance",[33,1400,648],{"class":69},[33,1402,1403],{"class":46},"now",[33,1405,1406],{"class":69},"() ",[33,1408,568],{"class":39},[33,1410,1411],{"class":50}," t0",[33,1413,565],{"class":69},[33,1415,1416],{"class":69},"}ms] ${",[33,1418,1307],{"class":50},[33,1420,654],{"class":69},[33,1422,337],{"class":50},[33,1424,1425,1428,1431],{"class":35,"line":154},[33,1426,1427],{"class":50},"  logEl.",[33,1429,1430],{"class":46},"append",[33,1432,1433],{"class":50},"(li);\n",[33,1435,1436],{"class":35,"line":370},[33,1437,79],{"class":50},[33,1439,1440],{"class":35,"line":385},[33,1441,86],{"emptyLinePlaceholder":85},[33,1443,1444,1446,1449,1451,1454,1456,1459,1461,1464,1466,1468],{"class":35,"line":394},[33,1445,1299],{"class":39},[33,1447,1448],{"class":46}," request",[33,1450,66],{"class":50},[33,1452,1453],{"class":590},"name",[33,1455,1129],{"class":50},[33,1457,1458],{"class":590},"ms",[33,1460,1129],{"class":50},[33,1462,1463],{"class":590},"fail",[33,1465,1129],{"class":50},[33,1467,1322],{"class":590},[33,1469,594],{"class":50},[33,1471,1472,1475,1477,1480,1482,1485,1487,1490],{"class":35,"line":403},[33,1473,1474],{"class":46},"  line",[33,1476,66],{"class":50},[33,1478,1479],{"class":69},"`${",[33,1481,1453],{"class":50},[33,1483,1484],{"class":69},"} 요청 시작`",[33,1486,1129],{"class":50},[33,1488,1489],{"class":69},"\"\"",[33,1491,1492],{"class":50},", id);\n",[33,1494,1495,1497,1499,1502,1505,1508,1510,1513,1516],{"class":35,"line":408},[33,1496,665],{"class":39},[33,1498,60],{"class":39},[33,1500,1501],{"class":602}," Promise",[33,1503,1504],{"class":50},"((",[33,1506,1507],{"class":590},"resolve",[33,1509,1129],{"class":50},[33,1511,1512],{"class":590},"reject",[33,1514,1515],{"class":50},") ",[33,1517,1518],{"class":39},"=>\n",[33,1520,1521,1524,1527,1530],{"class":35,"line":413},[33,1522,1523],{"class":46},"    setTimeout",[33,1525,1526],{"class":50},"(() ",[33,1528,1529],{"class":39},"=>",[33,1531,95],{"class":50},[33,1533,1534,1537],{"class":35,"line":418},[33,1535,1536],{"class":39},"      if",[33,1538,1539],{"class":50}," (fail) {\n",[33,1541,1542,1545,1547,1549,1551,1554,1556,1559],{"class":35,"line":430},[33,1543,1544],{"class":46},"        line",[33,1546,66],{"class":50},[33,1548,1479],{"class":69},[33,1550,1453],{"class":50},[33,1552,1553],{"class":69},"} 실패 ✗`",[33,1555,1129],{"class":50},[33,1557,1558],{"class":69},"\"fail\"",[33,1560,1492],{"class":50},[33,1562,1564,1567,1569,1572,1574,1576,1578,1580,1583],{"class":35,"line":1563},19,[33,1565,1566],{"class":46},"        reject",[33,1568,66],{"class":50},[33,1570,1571],{"class":39},"new",[33,1573,63],{"class":46},[33,1575,66],{"class":50},[33,1577,1479],{"class":69},[33,1579,1453],{"class":50},[33,1581,1582],{"class":69},"} 실패`",[33,1584,1585],{"class":50},"));\n",[33,1587,1589,1592,1595],{"class":35,"line":1588},20,[33,1590,1591],{"class":50},"      } ",[33,1593,1594],{"class":39},"else",[33,1596,95],{"class":50},[33,1598,1600,1602,1604,1606,1608,1611,1613,1616],{"class":35,"line":1599},21,[33,1601,1544],{"class":46},[33,1603,66],{"class":50},[33,1605,1479],{"class":69},[33,1607,1453],{"class":50},[33,1609,1610],{"class":69},"} 완료 ✓`",[33,1612,1129],{"class":50},[33,1614,1615],{"class":69},"\"ok\"",[33,1617,1492],{"class":50},[33,1619,1621,1624],{"class":35,"line":1620},22,[33,1622,1623],{"class":46},"        resolve",[33,1625,1626],{"class":50},"(name);\n",[33,1628,1630],{"class":35,"line":1629},23,[33,1631,1632],{"class":50},"      }\n",[33,1634,1636],{"class":35,"line":1635},24,[33,1637,1638],{"class":50},"    }, ms)\n",[33,1640,1642],{"class":35,"line":1641},25,[33,1643,1644],{"class":50},"  );\n",[33,1646,1648],{"class":35,"line":1647},26,[33,1649,79],{"class":50},[33,1651,1653],{"class":35,"line":1652},27,[33,1654,86],{"emptyLinePlaceholder":85},[33,1656,1658,1660,1663,1665],{"class":35,"line":1657},28,[33,1659,1244],{"class":39},[33,1661,1662],{"class":602}," scenarios",[33,1664,606],{"class":39},[33,1666,95],{"class":50},[33,1668,1670,1673,1676,1678,1680],{"class":35,"line":1669},29,[33,1671,1672],{"class":39},"  async",[33,1674,1675],{"class":46}," all",[33,1677,66],{"class":50},[33,1679,1322],{"class":590},[33,1681,594],{"class":50},[33,1683,1685,1688],{"class":35,"line":1684},30,[33,1686,1687],{"class":39},"    try",[33,1689,95],{"class":50},[33,1691,1693,1696,1699,1701,1703,1705,1707,1710,1713,1716,1718,1721,1723,1726,1728,1730,1733,1735,1737,1740,1742,1745,1747,1749,1751,1753,1755,1758,1760,1763,1765,1767],{"class":35,"line":1692},31,[33,1694,1695],{"class":39},"      const",[33,1697,1698],{"class":602}," r",[33,1700,606],{"class":39},[33,1702,375],{"class":39},[33,1704,1501],{"class":602},[33,1706,648],{"class":50},[33,1708,1709],{"class":46},"all",[33,1711,1712],{"class":50},"([",[33,1714,1715],{"class":46},"request",[33,1717,66],{"class":50},[33,1719,1720],{"class":69},"\"A\"",[33,1722,1129],{"class":50},[33,1724,1725],{"class":602},"600",[33,1727,1129],{"class":50},[33,1729,540],{"class":602},[33,1731,1732],{"class":50},", id), ",[33,1734,1715],{"class":46},[33,1736,66],{"class":50},[33,1738,1739],{"class":69},"\"B\"",[33,1741,1129],{"class":50},[33,1743,1744],{"class":602},"300",[33,1746,1129],{"class":50},[33,1748,526],{"class":602},[33,1750,1732],{"class":50},[33,1752,1715],{"class":46},[33,1754,66],{"class":50},[33,1756,1757],{"class":69},"\"C\"",[33,1759,1129],{"class":50},[33,1761,1762],{"class":602},"900",[33,1764,1129],{"class":50},[33,1766,540],{"class":602},[33,1768,1769],{"class":50},", id)]);\n",[33,1771,1773,1776,1778,1781,1784,1786,1788,1791],{"class":35,"line":1772},32,[33,1774,1775],{"class":46},"      line",[33,1777,66],{"class":50},[33,1779,1780],{"class":69},"`결과: ${",[33,1782,1783],{"class":50},"r",[33,1785,654],{"class":69},[33,1787,1129],{"class":50},[33,1789,1790],{"class":69},"\"result\"",[33,1792,1492],{"class":50},[33,1794,1796,1799,1801],{"class":35,"line":1795},33,[33,1797,1798],{"class":50},"    } ",[33,1800,129],{"class":39},[33,1802,132],{"class":50},[33,1804,1806,1808,1810,1813,1816,1818,1821,1824,1826,1828],{"class":35,"line":1805},34,[33,1807,1775],{"class":46},[33,1809,66],{"class":50},[33,1811,1812],{"class":69},"`catch: ${",[33,1814,1815],{"class":50},"e",[33,1817,648],{"class":69},[33,1819,1820],{"class":50},"message",[33,1822,1823],{"class":69},"} → A, C 결과는 못 받음 (요청은 계속 진행됨)`",[33,1825,1129],{"class":50},[33,1827,1790],{"class":69},[33,1829,1492],{"class":50},[33,1831,1833],{"class":35,"line":1832},35,[33,1834,1835],{"class":50},"    }\n",[33,1837,1839],{"class":35,"line":1838},36,[33,1840,1841],{"class":50},"  },\n",[33,1843,1845,1847,1850,1852,1854],{"class":35,"line":1844},37,[33,1846,1672],{"class":39},[33,1848,1849],{"class":46}," allSettled",[33,1851,66],{"class":50},[33,1853,1322],{"class":590},[33,1855,594],{"class":50},[33,1857,1859,1862,1864,1866,1868,1870,1872,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895,1897,1899,1901,1903,1905,1907,1909,1911,1913,1915,1917,1919,1921,1923],{"class":35,"line":1858},38,[33,1860,1861],{"class":39},"    const",[33,1863,1698],{"class":602},[33,1865,606],{"class":39},[33,1867,375],{"class":39},[33,1869,1501],{"class":602},[33,1871,648],{"class":50},[33,1873,1874],{"class":46},"allSettled",[33,1876,1712],{"class":50},[33,1878,1715],{"class":46},[33,1880,66],{"class":50},[33,1882,1720],{"class":69},[33,1884,1129],{"class":50},[33,1886,1725],{"class":602},[33,1888,1129],{"class":50},[33,1890,540],{"class":602},[33,1892,1732],{"class":50},[33,1894,1715],{"class":46},[33,1896,66],{"class":50},[33,1898,1739],{"class":69},[33,1900,1129],{"class":50},[33,1902,1744],{"class":602},[33,1904,1129],{"class":50},[33,1906,526],{"class":602},[33,1908,1732],{"class":50},[33,1910,1715],{"class":46},[33,1912,66],{"class":50},[33,1914,1757],{"class":69},[33,1916,1129],{"class":50},[33,1918,1762],{"class":602},[33,1920,1129],{"class":50},[33,1922,540],{"class":602},[33,1924,1769],{"class":50},[33,1926,1928,1931,1933,1935,1937,1939,1942,1944,1947,1949,1951,1953,1955,1957,1959,1962,1965,1968,1971,1973,1976,1979,1982,1985,1987,1989,1992,1994,1996,1999,2002,2004,2007,2009,2011,2013,2015],{"class":35,"line":1927},39,[33,1929,1930],{"class":46},"    line",[33,1932,66],{"class":50},[33,1934,1780],{"class":69},[33,1936,1783],{"class":50},[33,1938,648],{"class":69},[33,1940,1941],{"class":46},"map",[33,1943,1504],{"class":69},[33,1945,1946],{"class":602},"x",[33,1948,1515],{"class":69},[33,1950,1529],{"class":39},[33,1952,622],{"class":69},[33,1954,1946],{"class":50},[33,1956,648],{"class":69},[33,1958,651],{"class":50},[33,1960,1961],{"class":39}," ===",[33,1963,1964],{"class":69}," \"fulfilled\"",[33,1966,1967],{"class":39}," ?",[33,1969,1970],{"class":50}," x",[33,1972,648],{"class":69},[33,1974,1975],{"class":50},"value",[33,1977,1978],{"class":39}," +",[33,1980,1981],{"class":69}," \" 성공\"",[33,1983,1984],{"class":39}," :",[33,1986,1970],{"class":50},[33,1988,648],{"class":69},[33,1990,1991],{"class":50},"reason",[33,1993,648],{"class":69},[33,1995,1820],{"class":50},[33,1997,1998],{"class":69},")).",[33,2000,2001],{"class":46},"join",[33,2003,66],{"class":69},[33,2005,2006],{"class":69},"\", \"",[33,2008,565],{"class":69},[33,2010,654],{"class":69},[33,2012,1129],{"class":50},[33,2014,1790],{"class":69},[33,2016,1492],{"class":50},[33,2018,2020],{"class":35,"line":2019},40,[33,2021,1841],{"class":50},[33,2023,2025,2027,2030,2032,2034],{"class":35,"line":2024},41,[33,2026,1672],{"class":39},[33,2028,2029],{"class":46}," sequential",[33,2031,66],{"class":50},[33,2033,1322],{"class":590},[33,2035,594],{"class":50},[33,2037,2039,2041,2044,2046],{"class":35,"line":2038},42,[33,2040,1861],{"class":39},[33,2042,2043],{"class":602}," out",[33,2045,606],{"class":39},[33,2047,2048],{"class":50}," [];\n",[33,2050,2052,2055,2057,2059,2062,2065,2068,2070,2072,2074,2076,2078,2081,2084,2086,2088,2090,2093,2096,2098,2100],{"class":35,"line":2051},43,[33,2053,2054],{"class":39},"    for",[33,2056,622],{"class":50},[33,2058,1244],{"class":39},[33,2060,2061],{"class":602}," name",[33,2063,2064],{"class":39}," of",[33,2066,2067],{"class":50}," [",[33,2069,1720],{"class":69},[33,2071,1129],{"class":50},[33,2073,1739],{"class":69},[33,2075,1129],{"class":50},[33,2077,1757],{"class":69},[33,2079,2080],{"class":50},"]) out.",[33,2082,2083],{"class":46},"push",[33,2085,66],{"class":50},[33,2087,250],{"class":39},[33,2089,1448],{"class":46},[33,2091,2092],{"class":50},"(name, ",[33,2094,2095],{"class":602},"400",[33,2097,1129],{"class":50},[33,2099,540],{"class":602},[33,2101,2102],{"class":50},", id));\n",[33,2104,2106,2108,2110,2112,2115,2118,2120,2122],{"class":35,"line":2105},44,[33,2107,1930],{"class":46},[33,2109,66],{"class":50},[33,2111,1780],{"class":69},[33,2113,2114],{"class":50},"out",[33,2116,2117],{"class":69},"} (하나씩 기다려서 느림)`",[33,2119,1129],{"class":50},[33,2121,1790],{"class":69},[33,2123,1492],{"class":50},[33,2125,2127],{"class":35,"line":2126},45,[33,2128,1841],{"class":50},[33,2130,2132,2134,2137,2139,2141],{"class":35,"line":2131},46,[33,2133,1672],{"class":39},[33,2135,2136],{"class":46}," parallel",[33,2138,66],{"class":50},[33,2140,1322],{"class":590},[33,2142,594],{"class":50},[33,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2175,2177,2179,2181,2183,2185,2187,2189,2191,2193,2195],{"class":35,"line":2145},47,[33,2147,1861],{"class":39},[33,2149,2043],{"class":602},[33,2151,606],{"class":39},[33,2153,375],{"class":39},[33,2155,1501],{"class":602},[33,2157,648],{"class":50},[33,2159,1709],{"class":46},[33,2161,1712],{"class":50},[33,2163,1720],{"class":69},[33,2165,1129],{"class":50},[33,2167,1739],{"class":69},[33,2169,1129],{"class":50},[33,2171,1757],{"class":69},[33,2173,2174],{"class":50},"].",[33,2176,1941],{"class":46},[33,2178,1504],{"class":50},[33,2180,1453],{"class":590},[33,2182,1515],{"class":50},[33,2184,1529],{"class":39},[33,2186,1448],{"class":46},[33,2188,2092],{"class":50},[33,2190,2095],{"class":602},[33,2192,1129],{"class":50},[33,2194,540],{"class":602},[33,2196,2197],{"class":50},", id)));\n",[33,2199,2201,2203,2205,2207,2209,2212,2214,2216],{"class":35,"line":2200},48,[33,2202,1930],{"class":46},[33,2204,66],{"class":50},[33,2206,1780],{"class":69},[33,2208,2114],{"class":50},[33,2210,2211],{"class":69},"} (동시에 보내서 빠름)`",[33,2213,1129],{"class":50},[33,2215,1790],{"class":69},[33,2217,1492],{"class":50},[33,2219,2221],{"class":35,"line":2220},49,[33,2222,1841],{"class":50},[33,2224,2226],{"class":35,"line":2225},50,[33,2227,2228],{"class":50},"};\n",[33,2230,2232],{"class":35,"line":2231},51,[33,2233,86],{"emptyLinePlaceholder":85},[33,2235,2237,2240,2243,2245,2248,2251,2254,2256,2259,2261],{"class":35,"line":2236},52,[33,2238,2239],{"class":50},"document.",[33,2241,2242],{"class":46},"querySelectorAll",[33,2244,66],{"class":50},[33,2246,2247],{"class":69},"\"[data-run]\"",[33,2249,2250],{"class":50},").",[33,2252,2253],{"class":46},"forEach",[33,2255,1504],{"class":50},[33,2257,2258],{"class":590},"btn",[33,2260,1515],{"class":50},[33,2262,1518],{"class":39},[33,2264,2266,2269,2272,2274,2277,2280,2282],{"class":35,"line":2265},53,[33,2267,2268],{"class":50},"  btn.",[33,2270,2271],{"class":46},"addEventListener",[33,2273,66],{"class":50},[33,2275,2276],{"class":69},"\"click\"",[33,2278,2279],{"class":50},", () ",[33,2281,1529],{"class":39},[33,2283,95],{"class":50},[33,2285,2287,2289,2291,2293,2296],{"class":35,"line":2286},54,[33,2288,1861],{"class":39},[33,2290,921],{"class":602},[33,2292,606],{"class":39},[33,2294,2295],{"class":39}," ++",[33,2297,2298],{"class":50},"runId;\n",[33,2300,2302,2305,2307,2309],{"class":35,"line":2301},55,[33,2303,2304],{"class":50},"    logEl.innerHTML ",[33,2306,813],{"class":39},[33,2308,1317],{"class":69},[33,2310,337],{"class":50},[33,2312,2314,2317,2319,2322,2324],{"class":35,"line":2313},56,[33,2315,2316],{"class":50},"    t0 ",[33,2318,813],{"class":39},[33,2320,2321],{"class":50}," performance.",[33,2323,1403],{"class":46},[33,2325,104],{"class":50},[33,2327,2329],{"class":35,"line":2328},57,[33,2330,2331],{"class":50},"    scenarios[btn.dataset.run](id);\n",[33,2333,2335],{"class":35,"line":2334},58,[33,2336,2337],{"class":50},"  })\n",[33,2339,2341],{"class":35,"line":2340},59,[33,2342,73],{"class":50},[15,2344,2345,2347,2348,2350,2351,178,2354,2356],{},[19,2346,697],{},"을 누르면 B가 실패한 0.3초에 바로 ",[19,2349,129],{},"로 넘어가지만, ",[171,2352,2353],{},"A와 C의 요청은 그 뒤에도 계속 진행돼서 완료된다.",[19,2355,697],{},"은 요청을 취소하지 않고, 결과만 버린다.",[2358,2359,2361],"h3",{"id":2360},"어떤-걸-써야-할까","어떤 걸 써야 할까?",[488,2363,2364,2373],{},[491,2365,2366],{},[494,2367,2368,2370],{},[497,2369,499],{},[497,2371,2372],{},"추천",[511,2374,2375,2384,2393,2402],{},[494,2376,2377,2380],{},[516,2378,2379],{},"전부 있어야 의미가 있다 (상품 정보 + 가격 + 재고)",[516,2381,2382],{},[19,2383,697],{},[494,2385,2386,2389],{},[516,2387,2388],{},"일부가 실패해도 나머지는 보여준다 (대시보드 위젯 여러 개)",[516,2390,2391],{},[19,2392,747],{},[494,2394,2395,2398],{},[516,2396,2397],{},"여러 서버 중 가장 먼저 응답한 곳을 쓴다",[516,2399,2400],{},[19,2401,766],{},[494,2403,2404,2407],{},[516,2405,2406],{},"타임아웃과 경쟁시킨다",[516,2408,2409,2411,2412,2415],{},[19,2410,779],{}," (요즘은 ",[19,2413,2414],{},"AbortSignal.timeout","이 더 낫다)",[259,2417],{},[10,2419,2421,2422],{"id":2420},"함정-4-반복문-속-await","함정 4. 반복문 속 ",[19,2423,250],{},[2358,2425,2427,2430,2431,2433],{"id":2426},"forof-await는-하나씩-기다린다",[19,2428,2429],{},"for...of"," + ",[19,2432,250],{},"는 하나씩 기다린다",[24,2435,2437],{"className":26,"code":2436,"language":28,"meta":29,"style":29},"\u002F\u002F 각 요청이 0.2초라면 총 0.6초\nfor (const id of [1, 2, 3]) {\n  users.push(await getUser(id));\n}\n\n\u002F\u002F 동시에 보내면 총 0.2초\nconst users = await Promise.all([1, 2, 3].map(getUser));\n",[19,2438,2439,2444,2474,2491,2495,2499,2504],{"__ignoreMap":29},[33,2440,2441],{"class":35,"line":36},[33,2442,2443],{"class":150},"\u002F\u002F 각 요청이 0.2초라면 총 0.6초\n",[33,2445,2446,2449,2451,2453,2455,2457,2459,2461,2463,2466,2468,2471],{"class":35,"line":54},[33,2447,2448],{"class":39},"for",[33,2450,622],{"class":50},[33,2452,1244],{"class":39},[33,2454,921],{"class":602},[33,2456,2064],{"class":39},[33,2458,2067],{"class":50},[33,2460,1059],{"class":602},[33,2462,1129],{"class":50},[33,2464,2465],{"class":602},"2",[33,2467,1129],{"class":50},[33,2469,2470],{"class":602},"3",[33,2472,2473],{"class":50},"]) {\n",[33,2475,2476,2479,2481,2483,2485,2488],{"class":35,"line":76},[33,2477,2478],{"class":50},"  users.",[33,2480,2083],{"class":46},[33,2482,66],{"class":50},[33,2484,250],{"class":39},[33,2486,2487],{"class":46}," getUser",[33,2489,2490],{"class":50},"(id));\n",[33,2492,2493],{"class":35,"line":82},[33,2494,79],{"class":50},[33,2496,2497],{"class":35,"line":89},[33,2498,86],{"emptyLinePlaceholder":85},[33,2500,2501],{"class":35,"line":98},[33,2502,2503],{"class":150},"\u002F\u002F 동시에 보내면 총 0.2초\n",[33,2505,2506,2508,2511,2513,2515,2517,2519,2521,2523,2525,2527,2529,2531,2533,2535,2537],{"class":35,"line":107},[33,2507,1244],{"class":39},[33,2509,2510],{"class":602}," users",[33,2512,606],{"class":39},[33,2514,375],{"class":39},[33,2516,1501],{"class":602},[33,2518,648],{"class":50},[33,2520,1709],{"class":46},[33,2522,1712],{"class":50},[33,2524,1059],{"class":602},[33,2526,1129],{"class":50},[33,2528,2465],{"class":602},[33,2530,1129],{"class":50},[33,2532,2470],{"class":602},[33,2534,2174],{"class":50},[33,2536,1941],{"class":46},[33,2538,2539],{"class":50},"(getUser));\n",[15,2541,2542,2543,2430,2545,2547,2548,2551],{},"서로 의존하지 않는 요청이라면 ",[19,2544,1941],{},[19,2546,697],{},"로 동시에 보내는 게 훨씬 빠르다. 위 데모의 \"순차\"와 \"병렬\" 버튼으로 시간 차이를 확인할 수 있다. 반대로 ",[171,2549,2550],{},"앞 요청의 결과가 다음 요청에 필요하거나, 서버에 한꺼번에 부담을 주면 안 될 때","는 순차가 맞다.",[2358,2553,2555,2430,2557,2559],{"id":2554},"foreach-async는-기다리지-않는다",[19,2556,2253],{},[19,2558,40],{},"는 기다리지 않는다",[24,2561,2563],{"className":26,"code":2562,"language":28,"meta":29,"style":29},"const result = [];\n[1, 2, 3].forEach(async (id) => {\n  result.push(await getUser(id));\n});\nconsole.log(result); \u002F\u002F [] ← 아무것도 기다리지 않고 바로 넘어감\n",[19,2564,2565,2576,2609,2624,2629],{"__ignoreMap":29},[33,2566,2567,2569,2572,2574],{"class":35,"line":36},[33,2568,1244],{"class":39},[33,2570,2571],{"class":602}," result",[33,2573,606],{"class":39},[33,2575,2048],{"class":50},[33,2577,2578,2581,2583,2585,2587,2589,2591,2593,2595,2597,2599,2601,2603,2605,2607],{"class":35,"line":54},[33,2579,2580],{"class":50},"[",[33,2582,1059],{"class":602},[33,2584,1129],{"class":50},[33,2586,2465],{"class":602},[33,2588,1129],{"class":50},[33,2590,2470],{"class":602},[33,2592,2174],{"class":50},[33,2594,2253],{"class":46},[33,2596,66],{"class":50},[33,2598,40],{"class":39},[33,2600,622],{"class":50},[33,2602,1322],{"class":590},[33,2604,1515],{"class":50},[33,2606,1529],{"class":39},[33,2608,95],{"class":50},[33,2610,2611,2614,2616,2618,2620,2622],{"class":35,"line":76},[33,2612,2613],{"class":50},"  result.",[33,2615,2083],{"class":46},[33,2617,66],{"class":50},[33,2619,250],{"class":39},[33,2621,2487],{"class":46},[33,2623,2490],{"class":50},[33,2625,2626],{"class":35,"line":82},[33,2627,2628],{"class":50},"});\n",[33,2630,2631,2634,2636,2639],{"class":35,"line":89},[33,2632,2633],{"class":50},"console.",[33,2635,113],{"class":46},[33,2637,2638],{"class":50},"(result); ",[33,2640,2641],{"class":150},"\u002F\u002F [] ← 아무것도 기다리지 않고 바로 넘어감\n",[15,2643,2644,2646,2647,2649],{},[19,2645,2253],{},"는 콜백이 돌려주는 Promise를 무시한다. 그래서 반복은 바로 끝나고, 에러가 나도 바깥의 ",[19,2648,21],{},"가 잡지 못한다.",[2651,2652,2653],"warning",{},[237,2654,2655],{},[15,2656,2657,2659,2660,2662,2663,2665,2666,2430,2668,2670],{},[19,2658,2253],{}," 안에서는 ",[19,2661,250],{},"를 쓰지 말자. 순서대로 기다려야 하면 ",[19,2664,2429],{},", 동시에 보내도 되면 ",[19,2667,1941],{},[19,2669,697],{},"이다.",[259,2672],{},[10,2674,2676],{"id":2675},"함정-5-끝나지-않는-요청-타임아웃과-취소","함정 5. 끝나지 않는 요청: 타임아웃과 취소",[15,2678,2679,2681,2682,2685],{},[19,2680,473],{},"에는 기본 타임아웃이 없다. 서버가 응답하지 않으면 로딩 스피너가 계속 돈다. ",[19,2683,2684],{},"AbortSignal.timeout()","으로 제한 시간을 걸 수 있다.",[24,2687,2689],{"className":26,"code":2688,"language":28,"meta":29,"style":29},"try {\n  const res = await fetch(\"\u002Fapi\u002Freport\", { signal: AbortSignal.timeout(5000) });\n} catch (e) {\n  if (e.name === \"TimeoutError\") {\n    console.log(\"5초 안에 응답이 없어요\");\n  }\n}\n",[19,2690,2691,2697,2728,2736,2751,2765,2769],{"__ignoreMap":29},[33,2692,2693,2695],{"class":35,"line":36},[33,2694,92],{"class":39},[33,2696,95],{"class":50},[33,2698,2699,2701,2703,2705,2707,2709,2711,2714,2717,2720,2722,2725],{"class":35,"line":54},[33,2700,599],{"class":39},[33,2702,603],{"class":602},[33,2704,606],{"class":39},[33,2706,375],{"class":39},[33,2708,611],{"class":46},[33,2710,66],{"class":50},[33,2712,2713],{"class":69},"\"\u002Fapi\u002Freport\"",[33,2715,2716],{"class":50},", { signal: AbortSignal.",[33,2718,2719],{"class":46},"timeout",[33,2721,66],{"class":50},[33,2723,2724],{"class":602},"5000",[33,2726,2727],{"class":50},") });\n",[33,2729,2730,2732,2734],{"class":35,"line":76},[33,2731,126],{"class":50},[33,2733,129],{"class":39},[33,2735,132],{"class":50},[33,2737,2738,2740,2743,2746,2749],{"class":35,"line":82},[33,2739,619],{"class":39},[33,2741,2742],{"class":50}," (e.name ",[33,2744,2745],{"class":39},"===",[33,2747,2748],{"class":69}," \"TimeoutError\"",[33,2750,594],{"class":50},[33,2752,2753,2756,2758,2760,2763],{"class":35,"line":89},[33,2754,2755],{"class":50},"    console.",[33,2757,113],{"class":46},[33,2759,66],{"class":50},[33,2761,2762],{"class":69},"\"5초 안에 응답이 없어요\"",[33,2764,73],{"class":50},[33,2766,2767],{"class":35,"line":98},[33,2768,342],{"class":50},[33,2770,2771],{"class":35,"line":107},[33,2772,79],{"class":50},[15,2774,2775,2776,2779,2780,2783],{},"사용자가 페이지를 떠났거나 검색어를 바꿔서 ",[171,2777,2778],{},"이전 요청이 필요 없어졌을 때","는 ",[19,2781,2782],{},"AbortController","로 직접 취소한다.",[24,2785,2787],{"className":26,"code":2786,"language":28,"meta":29,"style":29},"let controller;\n\nasync function search(keyword) {\n  controller?.abort(); \u002F\u002F 이전 검색 요청 취소\n  controller = new AbortController();\n  try {\n    const res = await fetch(`\u002Fapi\u002Fsearch?q=${keyword}`, { signal: controller.signal });\n    return await res.json();\n  } catch (e) {\n    if (e.name === \"AbortError\") return; \u002F\u002F 일부러 취소한 건 에러가 아님\n    throw e;\n  }\n}\n",[19,2788,2789,2796,2800,2816,2829,2843,2849,2873,2885,2893,2914,2921,2925],{"__ignoreMap":29},[33,2790,2791,2793],{"class":35,"line":36},[33,2792,1267],{"class":39},[33,2794,2795],{"class":50}," controller;\n",[33,2797,2798],{"class":35,"line":54},[33,2799,86],{"emptyLinePlaceholder":85},[33,2801,2802,2804,2806,2809,2811,2814],{"class":35,"line":76},[33,2803,40],{"class":39},[33,2805,43],{"class":39},[33,2807,2808],{"class":46}," search",[33,2810,66],{"class":50},[33,2812,2813],{"class":590},"keyword",[33,2815,594],{"class":50},[33,2817,2818,2821,2824,2826],{"class":35,"line":82},[33,2819,2820],{"class":50},"  controller?.",[33,2822,2823],{"class":46},"abort",[33,2825,205],{"class":50},[33,2827,2828],{"class":150},"\u002F\u002F 이전 검색 요청 취소\n",[33,2830,2831,2834,2836,2838,2841],{"class":35,"line":89},[33,2832,2833],{"class":50},"  controller ",[33,2835,813],{"class":39},[33,2837,60],{"class":39},[33,2839,2840],{"class":46}," AbortController",[33,2842,104],{"class":50},[33,2844,2845,2847],{"class":35,"line":98},[33,2846,304],{"class":39},[33,2848,95],{"class":50},[33,2850,2851,2853,2855,2857,2859,2861,2863,2866,2868,2870],{"class":35,"line":107},[33,2852,1861],{"class":39},[33,2854,603],{"class":602},[33,2856,606],{"class":39},[33,2858,375],{"class":39},[33,2860,611],{"class":46},[33,2862,66],{"class":50},[33,2864,2865],{"class":69},"`\u002Fapi\u002Fsearch?q=${",[33,2867,2813],{"class":50},[33,2869,654],{"class":69},[33,2871,2872],{"class":50},", { signal: controller.signal });\n",[33,2874,2875,2877,2879,2881,2883],{"class":35,"line":123},[33,2876,311],{"class":39},[33,2878,375],{"class":39},[33,2880,668],{"class":50},[33,2882,671],{"class":46},[33,2884,104],{"class":50},[33,2886,2887,2889,2891],{"class":35,"line":135},[33,2888,323],{"class":50},[33,2890,129],{"class":39},[33,2892,132],{"class":50},[33,2894,2895,2898,2900,2902,2905,2907,2909,2911],{"class":35,"line":154},[33,2896,2897],{"class":39},"    if",[33,2899,2742],{"class":50},[33,2901,2745],{"class":39},[33,2903,2904],{"class":69}," \"AbortError\"",[33,2906,1515],{"class":50},[33,2908,267],{"class":39},[33,2910,971],{"class":50},[33,2912,2913],{"class":150},"\u002F\u002F 일부러 취소한 건 에러가 아님\n",[33,2915,2916,2918],{"class":35,"line":370},[33,2917,633],{"class":39},[33,2919,2920],{"class":50}," e;\n",[33,2922,2923],{"class":35,"line":385},[33,2924,342],{"class":50},[33,2926,2927],{"class":35,"line":394},[33,2928,79],{"class":50},[15,2930,2931],{},"이렇게 하면 \"늦게 도착한 이전 검색 결과가 최신 결과를 덮어쓰는\" 버그도 같이 막을 수 있다.",[259,2933],{},[10,2935,2937],{"id":2936},"에러를-어디서-어떻게-처리할까","에러를 어디서, 어떻게 처리할까",[2358,2939,2941,2942],{"id":2940},"원인을-잃지-않고-다시-던지기-cause","원인을 잃지 않고 다시 던지기: ",[19,2943,2944],{},"cause",[15,2946,2947,2948,2950],{},"낮은 단계의 에러를 잡아서 더 이해하기 쉬운 에러로 바꿀 때, 원래 에러를 ",[19,2949,2944],{},"에 담아두면 디버깅할 때 원인을 따라갈 수 있다.",[24,2952,2954],{"className":26,"code":2953,"language":28,"meta":29,"style":29},"try {\n  JSON.parse(text);\n} catch (err) {\n  throw new Error(\"설정 파일을 읽지 못했습니다\", { cause: err });\n}\n\u002F\u002F e.message → \"설정 파일을 읽지 못했습니다\"\n\u002F\u002F e.cause   → SyntaxError: ...\n",[19,2955,2956,2962,2975,2984,3000,3004,3009],{"__ignoreMap":29},[33,2957,2958,2960],{"class":35,"line":36},[33,2959,92],{"class":39},[33,2961,95],{"class":50},[33,2963,2964,2967,2969,2972],{"class":35,"line":54},[33,2965,2966],{"class":602},"  JSON",[33,2968,648],{"class":50},[33,2970,2971],{"class":46},"parse",[33,2973,2974],{"class":50},"(text);\n",[33,2976,2977,2979,2981],{"class":35,"line":76},[33,2978,126],{"class":50},[33,2980,129],{"class":39},[33,2982,2983],{"class":50}," (err) {\n",[33,2985,2986,2988,2990,2992,2994,2997],{"class":35,"line":82},[33,2987,57],{"class":39},[33,2989,60],{"class":39},[33,2991,63],{"class":46},[33,2993,66],{"class":50},[33,2995,2996],{"class":69},"\"설정 파일을 읽지 못했습니다\"",[33,2998,2999],{"class":50},", { cause: err });\n",[33,3001,3002],{"class":35,"line":89},[33,3003,79],{"class":50},[33,3005,3006],{"class":35,"line":98},[33,3007,3008],{"class":150},"\u002F\u002F e.message → \"설정 파일을 읽지 못했습니다\"\n",[33,3010,3011],{"class":35,"line":107},[33,3012,3013],{"class":150},"\u002F\u002F e.cause   → SyntaxError: ...\n",[2358,3015,3017],{"id":3016},"잡아서-삼키지-말자","잡아서 삼키지 말자",[24,3019,3021],{"className":26,"code":3020,"language":28,"meta":29,"style":29},"\u002F\u002F ❌ 에러를 잡고 아무것도 안 하면, 실패했다는 사실이 사라진다\ntry {\n  await save();\n} catch (e) {}\n",[19,3022,3023,3028,3034,3043],{"__ignoreMap":29},[33,3024,3025],{"class":35,"line":36},[33,3026,3027],{"class":150},"\u002F\u002F ❌ 에러를 잡고 아무것도 안 하면, 실패했다는 사실이 사라진다\n",[33,3029,3030,3032],{"class":35,"line":54},[33,3031,92],{"class":39},[33,3033,95],{"class":50},[33,3035,3036,3038,3041],{"class":35,"line":76},[33,3037,200],{"class":39},[33,3039,3040],{"class":46}," save",[33,3042,104],{"class":50},[33,3044,3045,3047,3049],{"class":35,"line":82},[33,3046,126],{"class":50},[33,3048,129],{"class":39},[33,3050,3051],{"class":50}," (e) {}\n",[15,3053,3054,3056,3057,3060],{},[19,3055,129],{},"에서는 셋 중 하나를 해야 한다. ",[171,3058,3059],{},"사용자에게 알리거나, 대체 값으로 복구하거나, 다시 던지거나."," 처리할 수 없는 곳에서는 잡지 말고 위로 올려 보내서, 처리할 수 있는 한 곳(화면 단, API 호출 계층)에서 모아서 다루는 게 좋다.",[2358,3062,3064,3065],{"id":3063},"마지막-안전망-unhandledrejection","마지막 안전망: ",[19,3066,3067],{},"unhandledrejection",[15,3069,3070],{},"아무도 잡지 않은 Promise 에러는 전역 이벤트로 받을 수 있다. 에러 모니터링 도구에 보내는 용도로 쓴다.",[24,3072,3074],{"className":26,"code":3073,"language":28,"meta":29,"style":29},"window.addEventListener(\"unhandledrejection\", (event) => {\n  sendToMonitoring(event.reason); \u002F\u002F 모니터링 서비스로 전송 (직접 만든 함수)\n});\n",[19,3075,3076,3100,3111],{"__ignoreMap":29},[33,3077,3078,3081,3083,3085,3088,3091,3094,3096,3098],{"class":35,"line":36},[33,3079,3080],{"class":50},"window.",[33,3082,2271],{"class":46},[33,3084,66],{"class":50},[33,3086,3087],{"class":69},"\"unhandledrejection\"",[33,3089,3090],{"class":50},", (",[33,3092,3093],{"class":590},"event",[33,3095,1515],{"class":50},[33,3097,1529],{"class":39},[33,3099,95],{"class":50},[33,3101,3102,3105,3108],{"class":35,"line":54},[33,3103,3104],{"class":46},"  sendToMonitoring",[33,3106,3107],{"class":50},"(event.reason); ",[33,3109,3110],{"class":150},"\u002F\u002F 모니터링 서비스로 전송 (직접 만든 함수)\n",[33,3112,3113],{"class":35,"line":76},[33,3114,2628],{"class":50},[15,3116,3117,3118,3121,3122,3125],{},"Node.js에서는 ",[19,3119,3120],{},"process.on(\"unhandledRejection\", ...)","이다. 이건 어디까지나 ",[171,3123,3124],{},"놓친 에러를 기록하는 용도","이지, 에러 처리를 대신하는 곳이 아니다.",[259,3127],{},[10,3129,3131],{"id":3130},"정리-왜-알아두면-좋은가","정리: 왜 알아두면 좋은가",[488,3133,3134,3144],{},[491,3135,3136],{},[494,3137,3138,3141],{},[497,3139,3140],{},"함정",[497,3142,3143],{},"올바른 처리",[511,3145,3146,3158,3173,3185,3197,3213,3229,3239,3249],{},[494,3147,3148,3153],{},[516,3149,3150,3152],{},[19,3151,21],{},"가 비동기 에러를 못 잡음",[516,3154,3155,3157],{},[19,3156,250],{},"를 붙인다",[494,3159,3160,3168],{},[516,3161,3162,3164,3165],{},[19,3163,92],{}," 안의 ",[19,3166,3167],{},"return promise",[516,3169,3170],{},[19,3171,3172],{},"return await promise",[494,3174,3175,3180],{},[516,3176,3177,3179],{},[19,3178,473],{},"가 404\u002F500에서도 성공",[516,3181,3182],{},[19,3183,3184],{},"if (!res.ok) throw ...",[494,3186,3187,3192],{},[516,3188,3189,3190],{},"하나 실패하면 전부 잃는 ",[19,3191,697],{},[516,3193,3194,3195],{},"일부 실패를 허용하면 ",[19,3196,1874],{},[494,3198,3199,3206],{},[516,3200,3201,2430,3203,3205],{},[19,3202,2429],{},[19,3204,250],{},"가 느림",[516,3207,3208,3209,2430,3211],{},"독립적인 요청은 ",[19,3210,1941],{},[19,3212,697],{},[494,3214,3215,3222],{},[516,3216,3217,2430,3219,3221],{},[19,3218,2253],{},[19,3220,40],{},"가 안 기다림",[516,3223,3224,3226,3227],{},[19,3225,2429],{}," 또는 ",[19,3228,697],{},[494,3230,3231,3234],{},[516,3232,3233],{},"끝나지 않는 요청",[516,3235,3236],{},[19,3237,3238],{},"AbortSignal.timeout(ms)",[494,3240,3241,3244],{},[516,3242,3243],{},"필요 없어진 요청",[516,3245,3246,3248],{},[19,3247,2782],{},"로 취소",[494,3250,3251,3254],{},[516,3252,3253],{},"원인을 잃는 에러 변환",[516,3255,3256],{},[19,3257,3258],{},"new Error(msg, { cause })",[15,3260,3261,3262,178,3265,3267,3268,3270,3271,3273,3274,3276,3277,3280],{},"비동기 에러는 동기 에러와 달리 ",[171,3263,3264],{},"조용히 사라지기 쉽다.",[19,3266,250],{}," 하나, ",[19,3269,509],{}," 한 줄, ",[19,3272,2253],{}," 대신 ",[19,3275,2429],{},"처럼 작은 차이가 \"에러가 났는데 아무도 모르는\" 상황과 \"실패를 제대로 알리고 복구하는\" 상황을 가른다. 비동기 코드를 쓸 때는 ",[171,3278,3279],{},"\"이 Promise가 실패하면 그 에러는 어디로 가는가?\""," 를 한 번씩 물어보자.",[3282,3283,3284],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":29,"searchDepth":54,"depth":54,"links":3286},[3287,3288,3290,3292,3296,3303,3304,3311],{"id":12,"depth":54,"text":13},{"id":263,"depth":54,"text":3289},"함정 1. return과 return await는 다르다",{"id":469,"depth":54,"text":3291},"함정 2. fetch는 404에서도 성공한다",{"id":693,"depth":54,"text":3293,"children":3294},"함정 3. Promise.all은 하나만 실패해도 전부 실패한다",[3295],{"id":2360,"depth":76,"text":2361},{"id":2420,"depth":54,"text":3297,"children":3298},"함정 4. 반복문 속 await",[3299,3301],{"id":2426,"depth":76,"text":3300},"for...of + await는 하나씩 기다린다",{"id":2554,"depth":76,"text":3302},"forEach + async는 기다리지 않는다",{"id":2675,"depth":54,"text":2676},{"id":2936,"depth":54,"text":2937,"children":3305},[3306,3308,3309],{"id":2940,"depth":76,"text":3307},"원인을 잃지 않고 다시 던지기: cause",{"id":3016,"depth":76,"text":3017},{"id":3063,"depth":76,"text":3310},"마지막 안전망: unhandledrejection",{"id":3130,"depth":54,"text":3131},"try\u002Fcatch로 감쌌는데 에러가 새어 나가고, 404인데 fetch는 성공했다고 한다. await 빠뜨린 try\u002Fcatch, return vs return await, fetch의 res.ok, Promise.all과 allSettled, 반복문 속 await, 타임아웃과 요청 취소까지 비동기 에러 처리의 함정을 직접 실행해보며 정리했다.","md",{"date":3315,"field":3316,"tags":3317},"2026.10.08","frontend",[3318],"javascript","\u002Fko\u002Fjavascript-async-error-handling",{"title":5,"description":3312},{"loc":3319},"ko\u002Fjavascript-async-error-handling","VsERnMjKGAaayaFDV6Hib8o2s1hgKgMBg6uXw2nVcV4",[3325,3336,3345,3353,3359,3367,3374,3381,3388,3399,3407,3415,3427,3435,3444,3450,3456,3463,3469,3475,3482,3485,3491,3497,3503,3510,3516,3523,3532,3540,3546,3553,3560,3566,3573,3580,3588,3595,3602,3611,3617,3624,3631,3637,3645,3652,3660,3666],{"path":3326,"title":3327,"description":3328,"meta":3329},"\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug","애드센스 ads.txt 경고를 받고 파헤치다가 찾은 진짜 색인 버그","구글 애드센스가 ads.txt 문제를 알려왔는데, 확인해보니 파일은 멀쩡했다. 대신 서치콘솔 색인 수가 적었던 진짜 원인을 Nuxt Content 슬러그 불일치 버그에서 찾은 과정을 정리한다.",{"date":3330,"field":3331,"tags":3332},"2026.09.17","blog",[3333,3334,3335],"seo","adsense","nuxt-content",{"path":3337,"title":3338,"description":3339,"meta":3340},"\u002Fko\u002Fapi-error-response-format","API 에러 응답 형식 설계하기: 제각각인 에러 응답을 RFC 9457 Problem Details로 통일하는 법","어떤 API는 문자열을, 어떤 API는 { error }를, 어떤 API는 { message, code }를 돌려준다면 프론트엔드는 에러마다 다른 코드를 써야 한다. 좋은 에러 응답의 조건과 표준 형식인 RFC 9457 Problem Details, 필드별 검증 에러, 내부 정보 숨기기, Node.js로 한 곳에서 처리하는 방법까지 정리했다.",{"date":3315,"field":3341,"tags":3342},"backend",[3343,3344],"api","http",{"path":3346,"title":3347,"description":3348,"meta":3349},"\u002Fko\u002Fcors-guide","CORS 에러 완전 정리: 왜 막히는지부터 preflight, credentials까지 올바르게 해결하기","'has been blocked by CORS policy' 에러는 서버가 아니라 브라우저가 막은 것이다. 같은 출처 정책, 단순 요청과 preflight, 쿠키를 보내는 요청, 응답 헤더 노출까지 실제 브라우저로 확인한 결과와 함께 정리하고, mode: 'no-cors' 같은 잘못된 해결법도 짚어봤다.",{"date":3315,"field":3341,"tags":3350},[3344,3351,3352],"web-security","nodejs",{"path":3354,"title":3355,"description":3356,"meta":3357},"\u002Fko\u002Fcss-has-selector","CSS :has() 선택자 실전 가이드: 자바스크립트 없이 부모를 선택하는 법","체크하면 카드가 강조되고, 입력이 틀리면 폼 전체가 반응하고, 별점에 마우스를 올리면 앞의 별까지 칠해진다. 예전엔 자바스크립트가 필요했던 일을 CSS :has() 한 줄로 해결하는 실전 예제를 직접 눌러보며 정리했다.",{"date":3315,"field":3316,"tags":3358},[949],{"path":3360,"title":3361,"description":3362,"meta":3363},"\u002Fko\u002Fcss-layouts","CSS Flexbox 완전 정복 가이드","Flexbox를 활용한 반응형 레이아웃 구현 방법과 실전 예제. 수직 정렬, 네비게이션 바, 카드 레이아웃까지 완벽 정리!",{"date":3364,"image":3365,"field":3316,"tags":3366},"2025.01.25","css.jpg",[949],{"path":3368,"title":3369,"description":3370,"meta":3371},"\u002Fko\u002Fcss-login-form","로그인","로그인 UI를 그려보자",{"date":3372,"field":3316,"tags":3373},"2025.03.29",[796,949],{"path":3375,"title":3376,"description":3377,"meta":3378},"\u002Fko\u002Fcsv-parser-cleaning","복잡한 CSV 데이터를 안전하게 파싱하는 방법","JavaScript에서 큰따옴표, 줄바꿈, 쉼표 등 복잡한 CSV 데이터를 안전하게 객체로 변환하는 전체 과정 설명",{"date":3379,"field":3316,"tags":3380},"2025.06.19",[3318],{"path":3382,"title":3383,"description":3384,"meta":3385},"\u002Fko\u002Fdebounce-guide","debounce와 throttle이란??","반복되는 함수 호출을 제어하는 기술인 디바운스(debounce)와 쓰로틀(throttle)을 알아보자.",{"date":3386,"field":3316,"tags":3387},"2025.03.26",[3318],{"path":3389,"title":3390,"description":3391,"meta":3392},"\u002Fko\u002Fdeclarative-vs-procedural-programming","선언적 프로그래밍 vs 절차형 프로그래밍","선언적 프로그래밍과 절차형 프로그래밍은 서로 대비되는 대표적인 패러다임이다. 두 패러다임의 차이를 알아보고, 각 패러다임에 해당하는 언어와 Vue\u002FReact의 특징을 살펴보자.",{"date":3393,"field":3394,"tags":3395},"2025.05.24","concepts",[3396,3397,3398],"paradigm","vue","react",{"path":3400,"title":3401,"description":3402,"meta":3403},"\u002Fko\u002Fdesign-guide","Design-Guide","A comprehensive guide on design principles and best practices for developers.",{"date":3404,"field":3316,"tags":3405},"2025.01.19",[3406],"design-system",{"path":3408,"title":3409,"description":3410,"meta":3411},"\u002Fko\u002Fdikw-pyramid","DIKW 피라미드 정리: 데이터가 지혜가 되기까지","Data, Information, Knowledge, Wisdom 4단계로 구성된 DIKW 피라미드 개념을 로그\u002F분석 데이터 같은 실제 개발 사례에 대입해서 정리한다.",{"date":3412,"field":3394,"tags":3413},"2026.09.18",[3414],"data",{"path":3416,"title":3417,"description":3418,"meta":3419},"\u002Fko\u002Fdisplaying-commits-with-github-actions","깃허브 액션으로 커밋 로그를 블로그에 띄우기: Nuxt와 Vercel 활용법","깃허브 액션을 통해 커밋 데이터를 자동으로 수집하고, Nuxt와 Vercel로 UI에 노출하는 방법을 소개합니다.",{"date":3420,"image":3421,"field":3422,"tags":3423},"2025.02.28","github-commits.jpg","infra",[3424,3425,3426],"github-actions","vercel","nuxt",{"path":3428,"title":3429,"description":3430,"meta":3431},"\u002Fko\u002Fdocker-auto-build-run","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기.",{"date":3432,"field":3422,"tags":3433},"2025.03.10",[3434],"docker",{"path":3436,"title":3437,"description":3438,"meta":3439},"\u002Fko\u002Fhow-to-mount-storage","라즈베리파이4에 저장장치 마운트 시키기","라즈베리파이에 저장장치를 연결하고 파티션 생성부터 포맷, 마운트까지의 전체 과정을 정리한 기록입니다.",{"date":3440,"field":3422,"tags":3441},"2025.07.11",[3442,3443],"raspberry-pi","linux",{"path":3445,"title":3446,"description":3446,"meta":3447},"\u002Fko\u002Fhtml","HTML이란 무엇인가",{"date":3364,"image":3448,"field":3316,"tags":3449},"html.jpg",[796],{"path":3451,"title":3452,"description":3453,"meta":3454},"\u002Fko\u002Fhtml-features-without-javascript","자바스크립트 없이 HTML만으로 되는 것들: 아직도 div로 다 만들고 있다면","아코디언, 팝오버, 모달, 자동완성, 폼 검증, 이미지 지연 로딩까지. 라이브러리 없이 HTML 태그와 속성만으로 해결되는 기능을 예제와 함께 정리했다. 코드는 줄고 접근성은 덤으로 따라온다.",{"date":3315,"field":3316,"tags":3455},[796],{"path":3457,"title":3458,"description":3459,"meta":3460},"\u002Fko\u002Fhtml2","미치광이 카드 컴포넌트","미치광이 카드 컴포넌트는 어떤 느낌일까",{"date":3461,"field":3316,"tags":3462},"2025.03.28",[796,949],{"path":3464,"title":3465,"description":3466,"meta":3467},"\u002Fko\u002Fhttp-cache-control-guide","HTTP 캐시 제대로 쓰기: Cache-Control, ETag, no-cache와 no-store의 차이","no-cache는 '캐시하지 마라'가 아니다. Cache-Control의 max-age, no-cache, no-store, s-maxage, immutable이 각각 어떻게 동작하는지 실제 브라우저로 서버에 요청이 몇 번 가는지 확인하고, ETag와 304, 배포해도 옛날 파일이 보이는 문제, Vercel CDN 캐시까지 정리했다.",{"date":3315,"field":3341,"tags":3468},[3344,3425],{"path":3470,"title":3471,"description":3472,"meta":3473},"\u002Fko\u002Fhttp-status-codes-guide","HTTP 상태 코드 올바르게 쓰기: 400 vs 422, 401 vs 403, 200 vs 201 vs 204 헷갈릴 때 보는 정리","에러인데 200에 success: false를 담고 있다면, 클라이언트와 모니터링 도구가 실패를 알 수 없다. 상황별로 어떤 상태 코드를 써야 하는지, 401과 403, 400과 422처럼 헷갈리는 코드의 차이, 301과 308처럼 POST가 GET으로 바뀌는 리다이렉트 함정까지 정리했다.",{"date":3315,"field":3341,"tags":3474},[3344,3343],{"path":3476,"title":3477,"description":3477,"meta":3478},"\u002Fko\u002Fhttps-guide","Let's Encrypt를 이용해서 HTTPS 적용하기",{"date":3479,"field":3422,"tags":3480},"2025.03.11",[3481],"https",{"path":3319,"title":5,"description":3312,"meta":3483},{"date":3315,"field":3316,"tags":3484},[3318],{"path":3486,"title":3487,"description":3488,"meta":3489},"\u002Fko\u002Fjavascript-event-loop","자바스크립트 이벤트 루프 완전 정복: 마이크로태스크와 매크로태스크의 실행 순서","setTimeout(0)보다 Promise.then이 먼저 실행되는 이유는? 콜 스택, 태스크 큐, 마이크로태스크 큐가 어떤 순서로 돌아가는지 예제로 정리하고, 브라우저와 Node.js의 차이, 실무에서 UI 멈춤을 피하는 방법까지 알아보자.",{"date":3315,"field":3394,"tags":3490},[3318,3352],{"path":3492,"title":3493,"description":3494,"meta":3495},"\u002Fko\u002Fjavascript-higher-order-functions","자바스크립트 고차 함수 완전 정복: map, filter, reduce부터 직접 만드는 once, memoize까지","함수를 인자로 받거나 함수를 반환하는 고차 함수. map, filter, reduce를 제대로 쓰는 법부터 once, memoize, pipe, 커링처럼 실무에서 바로 쓰는 고차 함수를 직접 만들어보고, ['1','2','3'].map(parseInt) 같은 함정까지 정리했다.",{"date":3315,"field":3316,"tags":3496},[3318],{"path":3498,"title":3499,"description":3500,"meta":3501},"\u002Fko\u002Fjavascript-nullish-coalescing-optional-chaining","자바스크립트 ?? vs || 완벽 정리: null, undefined, 0, 빈 문자열을 올바르게 처리하는 법","볼륨 0이 50으로 바뀌고, 빈 닉네임이 '익명'이 되는 버그는 대부분 || 때문이다. ??와 ||의 차이, ?. 옵셔널 체이닝, ??=, 기본 매개변수가 적용되는 조건, null 검사하는 올바른 방법까지 직접 값을 넣어보며 정리했다.",{"date":3315,"field":3316,"tags":3502},[3318],{"path":3504,"title":3505,"description":3506,"meta":3507},"\u002Fko\u002Fjs-data-01","JavaScript 데이터01","JavaScript 데이터 다루는 문제를 풀어보자",{"date":3508,"field":3316,"tags":3509},"2025.03.08",[3318],{"path":3511,"title":3512,"description":3513,"meta":3514},"\u002Fko\u002Fjs-data-02","JavaScript 데이터02","복잡한 데이터 구조를 다루는 실전 문제를 해결해보자.",{"date":3508,"field":3316,"tags":3515},[3318],{"path":3517,"title":3518,"description":3519,"meta":3520},"\u002Fko\u002Fjwt-vs-session-auth","JWT vs 세션 인증 비교: 무엇을 써야 할까? 저장 위치, 로그아웃, 보안까지 정리","JWT는 암호화가 아니라 서명이라서 누구나 내용을 읽을 수 있다. 세션과 JWT가 각각 어떻게 동작하는지, 강제 로그아웃이 어려운 이유, localStorage와 httpOnly 쿠키 중 어디에 저장해야 하는지, 리프레시 토큰까지 직접 토큰을 디코딩하고 변조해보며 정리했다.",{"date":3315,"field":3341,"tags":3521},[3522,3351],"auth",{"path":3524,"title":3525,"description":3526,"meta":3527},"\u002Fko\u002Fmarkdown-guid","마크다운 문법","마크다운 문법을 알아보자",{"date":3528,"image":3529,"field":3331,"tags":3530},"2025.03.02","markdown.webp",[3531],"markdown",{"path":3533,"title":3534,"description":3535,"meta":3536},"\u002Fko\u002Fminio-guide","MinIO 완벽 가이드: 설치부터 Node.js 연동까지","MinIO는 오픈소스 기반의 고성능 객체 스토리지 서버입니다. AWS S3와 호환되는 API를 제공하여 클라우드 네이티브 애플리케이션을 위한 완벽한 스토리지 솔루션으로 자리잡고 있습니다.",{"date":3537,"field":3341,"tags":3538},"2025.11.23",[3539,3352],"minio",{"path":3541,"title":3542,"description":3543,"meta":3544},"\u002Fko\u002Fnitro-wildcard-redirect-url-migration","글 주소 프리픽스를 통째로 바꿔야 할 때: Nitro wildcard 리다이렉트로 안전하게 옮기기","\u002Fblog\u002F[슬러그]로 쓰던 글 주소를 \u002Fko\u002F[슬러그]로 옮기면서, 기존 링크\u002F북마크를 하나도 안 깨뜨리고 처리한 방법. Nitro routeRules의 와일드카드 리다이렉트 패턴과 실제로 같이 바꿔야 했던 범위를 정리한다.",{"date":3315,"field":3331,"tags":3545},[3426,3333],{"path":3547,"title":3548,"description":3549,"meta":3550},"\u002Fko\u002Fnode-simple-server","Node.js 간단한 서버 만들기","Node.js로 간단한 HTTP 서버를 만드는 방법",{"date":3551,"field":3341,"tags":3552},"2025.01.30",[3352],{"path":3554,"title":3555,"description":3556,"meta":3557},"\u002Fko\u002Fnodejs-graceful-shutdown","Node.js 서버 안전하게 종료하기 (Graceful Shutdown): 배포할 때마다 요청이 끊기는 이유","배포나 재시작 순간 처리 중이던 요청이 끊긴다면 종료 신호(SIGTERM)를 처리하지 않았기 때문이다. server.close()로 진행 중인 요청을 마치고 끄는 법, keep-alive 연결 때문에 바로 안 꺼지는 함정, Docker와 PM2에서 신호가 전달되지 않는 문제까지 실제로 실행해 확인하며 정리했다.",{"date":3315,"field":3341,"tags":3558},[3352,3434,3559],"pm2",{"path":3561,"title":3562,"description":3563,"meta":3564},"\u002Fko\u002Fnuxt-colormode","Nuxt3 다크모드 적용하기","Nuxt3에 다크모드 적용하여 관리하기",{"date":3372,"field":3316,"tags":3565},[3426],{"path":3567,"title":3568,"description":3569,"meta":3570},"\u002Fko\u002Fnuxt-content-vercel-empty-query-sqlite","배포만 하면 블로그 글이 하나도 안 보이던 버그: Nuxt Content와 Vercel의 \u002Ftmp 문제","로컬에서는 멀쩡한데 Vercel에 올리면 queryCollection()이 빈 배열만 반환하던 버그를 추적한 과정. 원인은 @nuxt\u002Fcontent가 Vercel의 읽기 전용 파일시스템을 지원하기 전 버전에 고정돼 있었던 것.",{"date":3571,"field":3331,"tags":3572},"2026.10.07",[3335,3425],{"path":3574,"title":3575,"description":3576,"meta":3577},"\u002Fko\u002Fnuxt-runtimeconfig-appconfig","Nuxt 설정 runtimeConfig vs app.config 완전 정복","Nuxt3에서 전역 설정을 어떻게 관리하고, 어떤 기준으로 나누어야 할까? 다크모드 예시부터 실무 기준 설정 전략까지 총정리",{"date":3578,"field":3316,"tags":3579},"2025.06.16",[3426],{"path":3581,"title":3582,"description":3583,"meta":3584},"\u002Fko\u002Fnuxt-useasyncdata-cache-error","useAsyncData 캐싱 문제와 Key 동적 설정으로 해결하기","이전 포스트의 데이터가 계속 출력되는 캐싱 관련 문제를 해결하는 방법",{"date":3585,"field":3316,"tags":3586,"image":3587},"2025.01.24",[3426],"cache.jpg",{"path":3589,"title":3590,"description":3591,"meta":3592},"\u002Fko\u002Fnuxt-vercel-sql-error","Nuxt 3에서 Vercel 배포 시 발생한 md 파일 로딩 문제 해결기","Nuxt 3 웹 어플리케이션에서 Vercel 배포 환경에서 발생한 md 파일 로딩 문제를 분석하고 해결한 방법에 대해 공유합니다.",{"date":3585,"image":3593,"field":3316,"tags":3594},"ssg.jpg",[3426,3335,3425],{"path":3596,"title":3597,"description":3598,"meta":3599},"\u002Fko\u002Fnuxt3-pinia-guide","nuxt3 Pinia 적용하기","Nuxt3 프로젝트에서 Pinia를 적용해보자 @pinia\u002Fnuxt",{"date":3372,"field":3316,"tags":3600},[3426,3601],"pinia",{"path":3603,"title":3604,"description":3605,"meta":3606},"\u002Fko\u002Fnuxt3-unit-test","Nuxt 프로젝트에 유닛 테스트 코드 추가하기","Nuxt 3 프로젝트에서 Vitest를 활용한 테스트 환경 설정과 유틸리티 함수 테스트 방법",{"date":3607,"image":3608,"field":3316,"tags":3609},"2025.03.07","unit-test.jpg",[3426,3610],"vitest",{"path":3612,"title":3613,"description":3614,"meta":3615},"\u002Fko\u002Fpm2-guide","프로세스 매니저 PM2","배포환경에서 유용하게 사용할 수 있는 프로세스 매니저를 알아보자.",{"date":3432,"field":3422,"tags":3616},[3559,3352],{"path":3618,"title":3619,"description":3620,"meta":3621},"\u002Fko\u002Freact-createportal","React createPortal 완벽 가이드","React의 `createPortal`을 활용한 UI 구현법, 활용 사례, 장단점 및 코드 예제를 소개합니다.",{"date":3622,"field":3316,"tags":3623},"2025.01.27",[3398],{"path":3625,"title":3626,"description":3627,"meta":3628},"\u002Fko\u002Frouting","Routing","Nuxt file-system routing creates a route for every file in the pages\u002F directory.",{"date":3629,"field":3316,"tags":3630},"2025.01.22",[3426],{"path":3632,"title":3633,"description":3634,"meta":3635},"\u002Fko\u002Fsemantic-html-common-mistakes","의미 있는 HTML 쓰기: 대부분이 하는 시맨틱 HTML 실수 7가지","화면에서는 똑같아 보여도 div로 만든 버튼, label 없는 입력칸, 크기로 고른 제목 태그는 키보드 사용자와 스크린 리더, 검색 엔진에게 전혀 다르게 보인다. 직접 눌러볼 수 있는 예제로 흔한 시맨틱 HTML 실수와 고치는 법을 정리했다.",{"date":3315,"field":3316,"tags":3636},[796],{"path":3638,"title":3639,"description":3640,"meta":3641},"\u002Fko\u002Ftanstack-query-guide","TanStack Query란 무엇인가?","TanStack Query가 웹 애플리케이션에서 데이터 가져오기, 캐싱, 상태 관리를 어떻게 개선하는지 알아본다.",{"date":3642,"field":3316,"tags":3643},"2025.04.15",[3398,3644],"tanstack-query",{"path":3646,"title":3647,"description":3648,"meta":3649},"\u002Fko\u002Fuse-context","리엑트 상태관리 useContext()","React에서 `useContext`를 사용하면 컴포넌트 트리에 깊이 중첩된 컴포넌트에서도 prop drilling 없이 상태를 공유할 수 있습니다. 이 글에서는 `useContext`의 개념, 사용 방법, 장단점, 그리고 실전 예제를 함께 알아보겠습니다.",{"date":3650,"field":3316,"tags":3651},"2025.02.03",[3398],{"path":3653,"title":3654,"description":3655,"meta":3656},"\u002Fko\u002Fvite-plugin-style-inject","리엑티 컴포넌트를 패키지 빌드 시 CSS 같이 적용시키기","dist에 css에 파일이 생성되었으나, 적용 안 되는 이유를 알아보고 에러를 처리하자",{"date":3657,"field":3316,"tags":3658},"2025.04.06",[3659,3398],"vite",{"path":3661,"title":3662,"description":3663,"meta":3664},"\u002Fko\u002Fvue-component-guide","Vue의 \u003Ccomponent :is \u002F> 완벽 정리: 동적 렌더링의 모든 것","Vue에서 \u003Ccomponent :is \u002F>를 활용한 동적 컴포넌트 렌더링 방법과 VNode, 컴포넌트 객체의 차이를 실전 예제로 이해하기",{"date":3364,"field":3316,"tags":3665},[3397],{"path":3667,"title":3668,"description":3669,"meta":3670},"\u002Fko\u002Fvue-query-ref-value-trap-nuxt3","Vue Query에서 데이터가 안 보이는 이유? Nuxt 3와 Ref 구조의 함정","Nuxt 3 + vue-query를 사용할 때 API 데이터가 `v-if` 조건문에서 제대로 표시되지 않는 현상을 해결한 경험을 바탕으로, Ref 구조와 template 디렉티브에서의 차이를 자세히 설명합니다.",{"date":3671,"field":3316,"tags":3672},"2025.06.08",[3426,3397,3644],1791482016956]