---
# System prepended metadata

title: '[JavaScript 教學] Callback Function 是什麼？回呼函式和參數傳遞教學'
tags: [文章]

---

# [JavaScript 教學] Callback Function 是什麼？回呼函式和參數傳遞教學 - 用外送比喻一次搞懂回呼函式
![Callback Function](https://hackmd.io/_uploads/B1MbbRo9le.png)

最近開始接觸 React，發現跟 Vue 比起來更常用到 callback function 的概念，所以決定要來好好複習一下，到底什麼是 callback function！

## 什麼是 Callback Function?
用簡單白話一點的方式來說就是「傳進去、等前面的事情完成後再執行」的函式。

* 把一個函式當作參數「傳進去」另一個函式中
* 當某件事完成時，再「回過頭來」執行這個函式
* 所以叫做 callback (回呼)，事情做好了，「回頭來呼叫你」

> 想像我們打電話叫外送的時候：
> 在我們下訂單時，都會留一支聯絡電話📞（callback function）。
> 食物送到之後，外送員就會打那支電話通知我們（執行 callback function）。

Callback 並不是什麼神奇的語法或是艱深難懂的函式，只是 JS 把「函式可以當作變數傳來傳去」的這個能力，應用出來的一個機制。

## 👀 實際應用
以下就是一個 callback function 的簡單範例：
```javascript!
function greet(name, callback) {
  console.log(`Hi ${name}`);
  callback(); // 呼叫傳進來的函式
}

function sayBye() {
  console.log("Bye!");
}

greet("Pear", sayBye);
```
但是單看以上的例子，可能還是看不出來 callback function 的好用之處，其實我們平常在寫 JS 處理非同步操作的時候就很常用到，像是 `setTimeout`、`addEventListener`，以及 API 請求。

### setTimeout
```javascript!
setTimeout(sayHi, 1000); // 這裡的 sayHi 就是 callback function

function sayHi() {
    console.log('Hi Pear')
}
```
- 當 JavaScript 執行到 `setTimeout(sayHi, 1000);` 這行時，它會告訴瀏覽器或 Node.js 環境：「嘿，請你在 1 秒鐘之後，幫我執行一下 sayHi 這個函式。」
- JavaScript 不會等待這 1 秒鐘。它會繼續執行 setTimeout 後面的其他程式碼。
- 1 秒鐘過後，環境會自動去「回呼」之前傳給它的 `sayHi` 函式，這時就會在控制台看到 `Hi Pear`。

### API 請求
在 ES6 之前，也就是還沒有 Promise 跟 async/await 的時代，我們呼叫 API 通常不會立馬拿到資料，所以我們可以使用 callback function，等資料來了之後再執行我們想做的事。

```javascript!
function getUser(callback) {
  fetch("https://jsonplaceholder.typicode.com/users/1")
    .then(response => response.json())
    .then(data => {
      // 呼叫 callback，並把資料傳出去
      callback(null, data);
    })
    .catch(error => {
      // 若有錯誤，也傳給 callback 處理
      callback(error, null);
    });
}

getUser((err, userData) => {
  if (err) {
    console.error("發生錯誤：", err);
  } else {
    console.log("成功拿到使用者資料：", userData);
  }
});
```
上面的例子只是單純的想拿使用者資料，如果又新增了更多的需求：
`取得使用者資料 -> 根據使用者 ID 取得貼文 -> 根據貼文 ID 取得留言`
```javascript!
getUser((user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      console.log("Got everything!", comments);
    });
  });
});
```
此時的程式碼變得一層又一層，且不停地往下接力，看了非常痛苦😵‍💫，這就是所謂的 callback hell。
當有很多非同步操作必須「一個接一個執行」，而每一個都使用 callback function 嵌套在前一個裡面，導致程式變得很難閱讀跟維護，像掉進「地獄」一樣。

幸好我們現在有了新的做法！

**使用 Promise**
```javascript!
function getUser() {
  return fetch("https://jsonplaceholder.typicode.com/users/1")
    .then(res => res.json());
}

function getPosts(userId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
    .then(res => res.json());
}

function getComments(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`)
    .then(res => res.json());
}

getUser()
  .then(user => {
    console.log("使用者資料：", user);
    return getPosts(user.id);
  })
  .then(posts => {
    console.log("貼文資料：", posts);
    return getComments(posts[0].id);
  })
  .then(comments => {
    console.log("留言資料：", comments);
  })
  .catch(error => {
    console.error("發生錯誤：", error);
  });

```

**使用 async/await**
```javascript!
function getUser() {
  return fetch("https://jsonplaceholder.typicode.com/users/1")
    .then(res => res.json());
}

function getPosts(userId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
    .then(res => res.json());
}

function getComments(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`)
    .then(res => res.json());
}

async function fetchAll() {
  try {
    const user = await getUser();
    console.log("使用者資料：", user);

    const posts = await getPosts(user.id);
    console.log("貼文資料：", posts);

    const comments = await getComments(posts[0].id);
    console.log("留言資料：", comments);
  } catch (err) {
    console.error("發生錯誤：", err);
  }
}

fetchAll();
```
不得不說 async/await 的出現真是一大福音，處理 API 的非同步變得更加乾淨利落。

> 以前靠 callback，但會遇到 callback hell，ES6 開始有 Promise 改善，後來 ES8 用 async/await 更直覺。

## Callback Function 傳參數
Callback function 本身也是可以傳參數的，只要用**匿名函式**包起來就可以。
```javascript!
function greet(callback) {
  console.log("Hi");
  callback(); // 呼叫傳進來的函式
}

greet(() => {
  sayBye('Pear');
});

function sayBye(name) {
  console.log(`Bye ${name}！`);
}
```

---

## React 上的常見應用
### 事件處理（Event Handling）
React 重視「事件處理」，而事件處理 = callback
```jsx!
function MyButton() {
  const handleClick = () => {
    console.log('Clicked!');
  };

  return (
    <button onClick={handleClick}>Click me</button>
  );
}
```
`onClick` 接的就是一個 callback function，也就是當使用者點擊按鈕時才執行。
而且除了 `onClick`，還會碰到一堆像是：
* `onChange`
* `onSubmit`
* `onMouseEnter`
...只要是事件，就幾乎都會傳 callback。
### 父元件傳遞 callback 給子元件
傳函式當 props 下去，子組件再呼叫
```jsx!
// ParentComponent.jsx
import React from "react";
import ChildComponent from "./ChildComponent";

function ParentComponent() {
  const handleClickBtn = () => {
    console.log("點擊子組件的按鈕");
  };

  return <ChildComponent onClickBtn={handleClickBtn} />;
}

export default ParentComponent;
```
```jsx!
// ChildComponent.jsx
function ChildComponent({ onClickBtn }) {
  return <button onClick={onClickBtn}>Click me</button>;
}

export default ChildComponent;
```
把 `handleClickBtn` 傳進子組件，等子組件想「通知」父組件的時候，就會呼叫這個 function，這其實也是 callback。
### Hooks 中的 Callback Function (e.g., useEffect, useCallback, useMemo)
- `useEffect`: `useEffect` 的第一個參數就是一個 callback function，它會在元件渲染後執行。
```jsx!
import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('元件渲染或 count 改變');
    // 執行任何操作
  }, [count]); // 當 count 改變時執行

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Add</button>
    </div>
  );
}
```
這裡 `() => { ... }` 就是 `useEffect` 的 callback function。

---

希望這篇文章讓大家對 callback function 有了更清楚的認識。

這其實不是什麼神祕的黑魔法，而是 JavaScript 把函式可以當參數傳來傳去這個特性，活用到日常開發裡。

尤其像 React 這種框架，callback 幾乎是日常寫法的一部分。不管是事件處理、資料傳遞還是自訂 hook，都會遇到 callback。

學會 callback，也是邁向更進階 JavaScript / 前端開發的入門門檻。

Leetcode 題目也常常可以看到 callback function 的身影，可參考：
* [2635. Apply Transform Over Each Element in Array #Basic Array Transforms](https://medium.com/@liz665319/leetcode-30-days-of-lc-javascript-challenge-be92d98e0d64)
* [2634. Filter Elements from Array #Basic Array Transforms](https://medium.com/@liz665319/leetcode-30-days-of-lc-javascript-challenge-abf81249b44e)

此文同步發佈於 Medium：
[[JavaScript 教學] Callback Function 是什麼？回呼函式和參數傳遞教學](https://medium.com/@liz665319/javascript-callback-function-347084a19cd7)