了解callback hell、Promise、async/await

更新 發佈閱讀 27 分鐘

首先callback hell

callback hell叫做'回呼地獄',是指在處理多個相依的非同步操作時,過度巢狀使用回呼函數(callback functions)導致程式碼變得難以閱讀、理解和維護,形成金字塔形的縮排結構,也稱作「金字塔地獄」(Pyramid of Doom)

例如:

需求: 使用者點按鈕 → 送 API → 拿回資料再更新畫面

button.addEventListener("click", () => {
sendRequest(data => {
parseData(parsed => {
updateUI(() => {
console.log("完成")
})
})
})
})

像這樣一層包一層的結構,就是 callback hell 的樣子。




Promise:

Promise的定義物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。

意思是 Promise 代表 在進行非同步請求時,「成功的結果」「失敗的原因」

所以Promise只會有fulfilled(成功,拿到 value)跟 rejected(失敗,拿到 error)還有 一個中間狀態pending,等待中。

那基本語法就是:then/catch/finally

那建立基本的用法:

const p = new Promise((resolve, reject) => {
})

//​resolve 為成功取得的資料時,回傳資料。
//​reject 則會在失敗時回傳 error object。

每當傳入這兩個參數,函式就會直接執行,並在成功時執行resolve,在失敗時執行reject

而promise很常被搭配使用兩個function:

  • .then() 接住 resolve 的結果。
  • .catch() 接住 reject 的結果。

例如:

成功:

let example = new Promise((resolve, reject) => {  
//這邊Promise 可以想成:「我保證之後會給你結果」 的容器

resolve({type: "object"}); });
//Promise 立刻成功,成功的資料是一個物件 {type:"object"}

example.then((data)=>{console.log(data);});
//example.then,這邊.then 是「當 Promise 成功時,要做什麼」
//(data)就是你當初 resolve(...) 傳出去的那個資料:{ type: "object" }
//輸出{ type: "object" }

失敗:

let example = new Promise((resolve, reject) => {
//​建立了一個 Promise
//Promise 內部會給你兩個「控制結果」的函式:
//resolve(value):代表成功
//reject(error):代表失敗

reject(new Error("not allowed"));
//​代表這個 Promise 立刻變成失敗(rejected)
//並且失敗原因是一個 Error 物件,訊息是 "not allowed"
});

example.then((data)=>{console.log(data);})
//example.then,.then 只會在 Promise 成功(resolve) 時執行
//但你前面是 reject,所以這個 .then 會被跳過,不會跑

example.catch((error) => {console.log(error);});
//​.catch 只會在 Promise 失敗(reject) 時執行
//所以它會跑,並印出錯誤物件

promise的function是可以被串接起來的,叫做 Promise Chain

舉例:

let example = new Promise((resolve, reject) => { 

resolve({ string : "Hello" });

});

example.then((data)=>{console.log(data);})
//先印出物件 { string: "Hello" }

example.then(() => {console.log("HI");})
//再印出 "HI"
//而第二個會跑是因為​第一個 .then(...) 成功執行完之後
//它會回傳一個「新的 Promise」
//你在第一個 then 裡沒有 throw 錯誤
//所以它是「成功狀態」
//於是下一個 .then(...) 就接著跑

example.catch((error) => {console.log(error);});
//​只要Promise失敗,就會跳到這邊處理錯誤




async/await:

async:

  • 放在函式定義前,表示該函式是非同步的。
  • async 函式總是返回一個 Promise
  • 若函式返回一個值,該 Promise 狀態為 resolved
  • 若拋出錯誤,該 Promise 狀態為 rejected

await:

  • 只能在 async 函式內部使用
  • 用於等待一個 Promise 的結果。
  • 暫停 async 函式執行,直到 Promise 解析完成,並返回解析後的值。 


舉例:

//基本語法

async function name([param[, param[, ... param]]]) {
statements
}

// ​name 函式名稱。
//​ param 傳遞至函式的參數名稱。
// ​statements 組成該函式主體的陳述。


原本的原本 Promise 的寫法(then / catch):

function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Hello");
}, 1000);
});
}

getData()
.then((data) => {
console.log("成功:", data);
})
.catch((error) => {
console.log("失敗:", error);
});

//.then(data => ...) → 拿 resolve 的值
//.catch(error => ...) → 拿 reject 的錯誤


用 async / await:

async function run() {
try {
const data = await getData(); //等 Promise resolve
console.log("成功:", data);
} catch (error) {
console.log("失敗:", error); //接 Promise reject
}
}

run();

//​await getData()
//等 Promise 完成,直接拿到 resolve 的值

//如果 Promise 失敗
//會丟進 catch(就像 throw error)

那還有一個 fetch
fetch 就是用來「向後端拿資料(API)」的工具,而且它「回傳 Promise」

使用async/await 寫:

async function loadTodo() {
try {
const response = await fetch("網址");
const data = await response.json();
console.log(data);
} catch (error) {
console.log("錯誤", error);
}
}

loadTodo();


那我們來做直接範例:

api網址:https://randomuser.me/

這個是這個api裡面package,所包的東西。

因為我們只想要取裡面的圖片,而包住整個圖片內容的東西叫picture,而我們只要使用最大張large就可以了!

api裡面的東西範例:

{
"results": [
{
"gender": "female",
"name": {
"title": "Miss",
"first": "Jennie",
"last": "Nichols"
},
"location": {
"street": {
"number": 8929,
"name": "Valwood Pkwy",
},
"city": "Billings",
"state": "Michigan",
"country": "United States",
"postcode": "63104",
"coordinates": {
"latitude": "-69.8246",
"longitude": "134.8719"
},
"timezone": {
"offset": "+9:30",
"description": "Adelaide, Darwin"
}
},
"email": "[email protected]",
"login": {
"uuid": "7a0eed16-9430-4d68-901f-c0d4c1c3bf00",
"username": "yellowpeacock117",
"password": "addison",
"salt": "sld1yGtd",
"md5": "ab54ac4c0be9480ae8fa5e9e2a5196a3",
"sha1": "edcf2ce613cbdea349133c52dc2f3b83168dc51b",
"sha256": "48df5229235ada28389b91e60a935e4f9b73eb4bdb855ef9258a1751f10bdc5d"
},
"dob": {
"date": "1992-03-08T15:13:16.688Z",
"age": 30
},
"registered": {
"date": "2007-07-09T05:51:59.390Z",
"age": 14
},
"phone": "(272) 790-0888",
"cell": "(489) 330-2385",
"id": {
"name": "SSN",
"value": "405-88-3636"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "US"
}
],
"info": {
"seed": "56d27f4a53bd5441",
"results": 1,
"page": 1,
"version": "1.4"
}
}

首先我們要先設定HTML:

HTML:

// HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="index.css">
    <title>Document</title>
  </head>

  <body>
    <div class="wrap">
      <div class="content">
        <button id="test-btn">Click me</button>
        <img
          id="result-img"
          src=""
          alt="result"
          width="300"
        />
      </div>

      <div class="word">
        <p id="national"></p>
        <p id="email"></p>
        <p id="gender"></p>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
//引入axios
    <script src="index.js"></script> // 引入javascript
  </body>
</html>

順便設定CSS在上面:

CSS:

//css​

.wrap{
  display: grid;
  justify-items: center;
}

.content{
  display: grid;
  justify-items: center;
  margin-top: 20px;
}

#test-btn{
  margin-bottom: 30px;
}

#result-img{
  margin-bottom: 30px;
}

那我們javascript要怎麼做呢?

範例如下:

javascript:

//javascript​
const button = document.getElementById('test-btn')
const image = document.getElementById('result-img')
const nationalEl = document.getElementById('national')
const emailEl = document.getElementById('email')
const genderEl = document.getElementById('gender')



async function seriesConnectionAPI() {
  try{
    const response = await axios.get('https://randomuser.me/api/')
// 這邊取api的網址
    const user = response.data.results[0]
// 這邊的results是這個api裡面,由於整包叫result
// 因為要取key,使用.result來取
// 前面我們使用變數response來取得api
// 所以這邊使用const user的方式來取整包
// 所以用user = response.data.result
// response這上面api的網址
// .data是指為了取出此結構中名為 "data" 的屬性
// 而.reslts是指整包叫result的東西
    const photo = user.picture.large
// 這邊的取圖片
// 我們定義變數photo,去取裡面的內容
// user是裡面整包的東西
// picture是整包裡面都是包圖片的,所以我們使用.picture來取
// 然後我們只要最大張,所以使用.large


    console.log(user) // 我們先印user
    image.src = photo // 取圖片photo
    nationalEl.textContent = `${user.location.country}${user.location.city}`
    emailEl.textContent = `Email: ${user.email}`
    genderEl.textContent = `Gender: ${user.gender}`
  } catch(error){
    console.error(error)
  }
}


seriesConnectionAPI()
button.addEventListener('click' , seriesConnectionAPI)


那如果我們後面需要取很多資料的時候我們該怎麼做呢?

我們先看api:https://picsum.photos/v2/list

基本HTML組成:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Template</title>
  <!-- CSS import below -->
</head>

<body>
  <!-- Write your HTML here -->
  <button id="test_bun">點擊我</button>
  <img
    id="test_img"
    src=""
    alt=""
    width="300"
  >
  <div id="img_list"></div>



  <!-- JS import below -->
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="test_01.js"></script>
</body>
</html>


javascript:

const buttonEl = document.getElementById('test_bun')
const imgEl = document.getElementById('test_img')
const imgListEl = document.getElementById('img_list')

const apiUrl = 'https://picsum.photos/v2/list'
// API網址

async function testAPI() {
  try{
    const response = await axios.get(apiUrl)
// 定義一個變數​response去接住axios.get拿到的API

    const user = response.data
// 定義變數user,去拿到api裡面的資料
// 那如果是只單純要拿第一個就這樣寫:const user = response.data[0]
   
const fivePhoto =
user.slice().sort(() => Math.random() -0.5).slice(0, 5)
// 我們先使用user.slice()
// 而這邊的.slice()會將user裡面的陣列給複製一份

// 而.sort()這段在做什麼呢?
// .sort()這邊會將原本陣列裡面的元素,我們想排列的方式重新排列
// 它會改變原本的陣列。
// 當回傳 < 0:a 排在 b 前面,而回傳 > 0:a 排在 b 後面

// 而後面.sort(() => Math.random() -0.5)的Math.random() -0.5在做什麼呢?
// Math.random()他是個產生亂數的東西,範圍會收成-0.5 ~ +0.5
// 再讓.sort 每次比較都「隨機決定前後順序」
// 而.slice(0, 5),.slice()的括弧裡面在做的事是,我們先預設.slice(a, b)

// 設定裡面的參數從哪裡開始.slice(a, b)裡面的a
// 設定到哪邊結束就是.slice(a, b)裡面的b
// 但是這邊要注意的事情是.slice()也是會回傳新陣列的,但是原本的陣列不會進行修改
// 而這邊設定.slice(0, 5),就是取前五筆的五筆資料
// 在設定變數 fivePhoto 去接住結果。
// 總之這整段在做的事情是:
// 先複製user裡面的陣列再透過.sort(() => Math.random() -0.5方式去打亂
// 在使用.slice()去取前五筆資料


    imgListEl.innerHTML = ''
// 這邊​.innerHTML代表「某個元素裡面包含的 HTML 內容(字串)」
// 他會將前面html裡面設定的<div id="img_list"></div>的東西清空


    console.log('user:', user)


    fivePhoto.forEach((photograph) => {
// 這邊使用forEach來遍歷fivePhoto裡面的資料,而箭頭函式裡面的photograph代表裡面的物件

      const img = document.createElement('img')
// 使用變數 img ​在記憶體中做出一個 img 元素

      img.src = photograph.download_url
// 然後再取圖片的網址出來塞進img.src裡面

      img.width = 300
// ​圖片顯示寬度設定為 300

      const author = document.createElement('photograph')
// ​使用變數 author 在記憶體中做出一個 photograph 元素

      author.textContent = `'Author': ${photograph.author}`
// 設定作者的文字內容​,.textContent 會在裡面設定純文字內容
// 而樣板字串裡面 photograph.author,取裡面的作者 author

      imgListEl.appendChild(img)
// .appendChild 這個會將變數 img 這個子元素加到容器裡

      imgListEl.appendChild(author)
// .appendChild 這個會將​變數 author 這個子元素加到容器裡
    })
    console.log('imgEl:', imgEl)
    console.log('response:',response)
  }catch(error){
    console.log(error)
  }
}


testAPI() // 進行函式輸出
buttonEl.addEventListener('click', testAPI)
// 將按鈕綁定,去​click觸及函式 testAPI





留言
avatar-img
睿哲楊的沙龍
2會員
18內容數
睿哲楊的沙龍的其他內容
2026/01/15
首先我們要先了解非同步 & 同步! 我們先講同步: 同步的意思是說,我們正常的程式碼都是一行一行做,如果前一行的程式碼沒有做完,下一行就不能做,說白一點就是一件事情沒做完,下一件事情就不能做。 範例: function step1() { console.log("第一步") } fu
2026/01/15
首先我們要先了解非同步 & 同步! 我們先講同步: 同步的意思是說,我們正常的程式碼都是一行一行做,如果前一行的程式碼沒有做完,下一行就不能做,說白一點就是一件事情沒做完,下一件事情就不能做。 範例: function step1() { console.log("第一步") } fu
2025/12/29
字串 String: 字串(String)是一連串字元組成的序列,是程式設計中表示和處理文字資料的基礎類型,可由字母、數字、符號或空格組成,通常用雙引號括起來,而字串最常出現的地方:表單輸入、搜尋、顯示文案、格式化資料(電話/日期/金額)、做驗證(是否空白、長度、是否含特定字)。 取得長度:
2025/12/29
字串 String: 字串(String)是一連串字元組成的序列,是程式設計中表示和處理文字資料的基礎類型,可由字母、數字、符號或空格組成,通常用雙引號括起來,而字串最常出現的地方:表單輸入、搜尋、顯示文案、格式化資料(電話/日期/金額)、做驗證(是否空白、長度、是否含特定字)。 取得長度:
2025/12/14
javascript有分兩大類型別:原始型別 (Primitive Types) 和物件型別 (Object Types)兩種,而原始型別包含string、number、boolean、null、undefined、Symbol,物件型別則是包含陣列、函式、日期等。 這次我們來了解原始型別:
2025/12/14
javascript有分兩大類型別:原始型別 (Primitive Types) 和物件型別 (Object Types)兩種,而原始型別包含string、number、boolean、null、undefined、Symbol,物件型別則是包含陣列、函式、日期等。 這次我們來了解原始型別:
看更多
你可能也想看
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
帶學生設計班規不僅可用於寫作訓練,加強議論能力,也可以運用在班級事務的管理上,讓學生自由提案,然後在班會一同討論、表決、擬定獎懲辦法,打造公民和法律素養。
Thumbnail
帶學生設計班規不僅可用於寫作訓練,加強議論能力,也可以運用在班級事務的管理上,讓學生自由提案,然後在班會一同討論、表決、擬定獎懲辦法,打造公民和法律素養。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
本文主要介紹以下的會計知識: 1.現金的角色與定義。 2.長短期現金需求。 3.現金的管理方式。 4.零用金制度。
Thumbnail
本文主要介紹以下的會計知識: 1.現金的角色與定義。 2.長短期現金需求。 3.現金的管理方式。 4.零用金制度。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
在台灣,不動產繼承是常見的法律問題,不僅涉及複雜的法律條款,還關乎家族和諧。無論富裕家庭還是一般家庭,處理不動產繼承的程序和稅務規範都是繼承人必須面對的挑戰。以下重點將帶您了解不動產繼承的基本程序、法定繼承順序以及特留分的概念,協助您順利完成繼承事宜。 一、什麼是不動產繼承? 不動產在法律上涵蓋
Thumbnail
在台灣,不動產繼承是常見的法律問題,不僅涉及複雜的法律條款,還關乎家族和諧。無論富裕家庭還是一般家庭,處理不動產繼承的程序和稅務規範都是繼承人必須面對的挑戰。以下重點將帶您了解不動產繼承的基本程序、法定繼承順序以及特留分的概念,協助您順利完成繼承事宜。 一、什麼是不動產繼承? 不動產在法律上涵蓋
Thumbnail
爬山和人生很相似,必須帶著一股無可救藥的衝勁與堅持,同時具備著隨時撤退的勇氣。 「我們不需要全然了解一個人,才能對他好,或者愛他。我們終究無法全然了解另一個人,說不定,連我們自己都不完全了解自己。我們會因為愈來愈不了解對方,而感到焦慮。此時請記得,要回過頭多愛自己。一個人身上散發出的自信,不待張揚
Thumbnail
爬山和人生很相似,必須帶著一股無可救藥的衝勁與堅持,同時具備著隨時撤退的勇氣。 「我們不需要全然了解一個人,才能對他好,或者愛他。我們終究無法全然了解另一個人,說不定,連我們自己都不完全了解自己。我們會因為愈來愈不了解對方,而感到焦慮。此時請記得,要回過頭多愛自己。一個人身上散發出的自信,不待張揚
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News