回调函数

回调函数概念

回调函数是作为参数传递给另一个函数的函数。

什么是回调函数

// 回调函数:被作为参数传递的函数
function greet(name, callback) {
    console.log("你好, " + name);
    callback();
}

greet("东巴文", function() {
    console.log("欢迎访问 db-w.cn");
});

// 输出:
// 你好, 东巴文
// 欢迎访问 db-w.cn

回调函数特点

// 1. 函数作为参数
function processArray(arr, callback) {
    const result = [];
    for (const item of arr) {
        result.push(callback(item));
    }
    return result;
}

const doubled = processArray([1, 2, 3], function(x) {
    return x * 2;
});
console.log(doubled);  // [2, 4, 6]

// 2. 可以有返回值
function calculate(a, b, operation) {
    return operation(a, b);
}

const sum = calculate(5, 3, (a, b) => a + b);
const product = calculate(5, 3, (a, b) => a * b);
console.log(sum, product);  // 8, 15

// 3. 可以是命名函数
function logItem(item) {
    console.log(item);
}

[1, 2, 3].forEach(logItem);

回调函数应用

回调函数的常见应用场景。

数组方法回调

const numbers = [1, 2, 3, 4, 5];

// forEach
numbers.forEach(function(item, index, array) {
    console.log(`索引${index}: ${item}`);
});

// map
const doubled = numbers.map(function(item) {
    return item * 2;
});
console.log(doubled);  // [2, 4, 6, 8, 10]

// filter
const evens = numbers.filter(function(item) {
    return item % 2 === 0;
});
console.log(evens);  // [2, 4]

// reduce
const sum = numbers.reduce(function(acc, item) {
    return acc + item;
}, 0);
console.log(sum);  // 15

// find
const found = numbers.find(function(item) {
    return item > 3;
});
console.log(found);  // 4

// some
const hasEven = numbers.some(function(item) {
    return item % 2 === 0;
});
console.log(hasEven);  // true

// every
const allPositive = numbers.every(function(item) {
    return item > 0;
});
console.log(allPositive);  // true

// sort
const sorted = [3, 1, 4, 1, 5].sort(function(a, b) {
    return a - b;
});
console.log(sorted);  // [1, 1, 3, 4, 5]

事件处理回调

// DOM事件
document.querySelector("button").addEventListener("click", function(e) {
    console.log("按钮被点击");
});

// 定时器回调
setTimeout(function() {
    console.log("1秒后执行");
}, 1000);

setInterval(function() {
    console.log("每秒执行");
}, 1000);

// 事件监听
const emitter = new EventTarget();
emitter.addEventListener("custom", function(e) {
    console.log("自定义事件:", e.detail);
});

异步操作回调

// 文件读取(Node.js)
const fs = require("fs");

fs.readFile("file.txt", "utf8", function(err, data) {
    if (err) {
        console.error("读取失败:", err);
        return;
    }
    console.log("文件内容:", data);
});

// AJAX请求
function fetchData(url, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = function() {
        if (xhr.status === 200) {
            callback(null, xhr.responseText);
        } else {
            callback(new Error("请求失败"));
        }
    };
    xhr.onerror = function() {
        callback(new Error("网络错误"));
    };
    xhr.send();
}

fetchData("/api/data", function(err, data) {
    if (err) {
        console.error(err);
        return;
    }
    console.log(data);
});

自定义回调

// 带回调的工具函数
function loadData(url, onSuccess, onError) {
    fetch(url)
        .then(response => response.json())
        .then(data => onSuccess(data))
        .catch(error => onError(error));
}

loadData(
    "/api/users",
    function(data) {
        console.log("成功:", data);
    },
    function(error) {
        console.error("失败:", error);
    }
);

// 多个回调
function processFile(file, options) {
    const {
        onStart,
        onProgress,
        onComplete,
        onError
    } = options;
    
    onStart && onStart(file);
    
    // 模拟处理过程
    let progress = 0;
    const interval = setInterval(() => {
        progress += 10;
        onProgress && onProgress(progress);
        
        if (progress >= 100) {
            clearInterval(interval);
            onComplete && onComplete();
        }
    }, 100);
}

processFile("data.txt", {
    onStart: (file) => console.log("开始处理:", file),
    onProgress: (p) => console.log("进度:", p + "%"),
    onComplete: () => console.log("处理完成"),
    onError: (e) => console.error("错误:", e)
});

回调地狱

多层嵌套回调导致的代码难以维护。

回调地狱示例

// 典型的回调地狱
getUser(userId, function(user) {
    getPosts(user.id, function(posts) {
        getComments(posts[0].id, function(comments) {
            getAuthor(comments[0].authorId, function(author) {
                console.log(author.name);
            });
        });
    });
});

// 实际例子
fs.readFile("config.json", "utf8", function(err, configData) {
    if (err) {
        console.error(err);
        return;
    }
    
    const config = JSON.parse(configData);
    
    fs.readFile(config.dataFile, "utf8", function(err, data) {
        if (err) {
            console.error(err);
            return;
        }
        
        processData(data, function(err, result) {
            if (err) {
                console.error(err);
                return;
            }
            
            fs.writeFile(config.outputFile, result, function(err) {
                if (err) {
                    console.error(err);
                    return;
                }
                
                console.log("处理完成");
            });
        });
    });
});

回调地狱的问题

// 问题1:代码难以阅读
// 金字塔形状的代码结构

// 问题2:错误处理复杂
async1(function(err, result1) {
    if (err) return handleError(err);
    
    async2(result1, function(err, result2) {
        if (err) return handleError(err);
        
        async3(result2, function(err, result3) {
            if (err) return handleError(err);
            // 每层都要处理错误
        });
    });
});

// 问题3:变量作用域混乱
async1(function(result1) {
    var a = 1;
    
    async2(function(result2) {
        var b = 2;
        // a和b都在这里可见
        
        async3(function(result3) {
            // 变量作用域难以追踪
        });
    });
});

// 问题4:难以复用
// 嵌套的回调难以提取和复用

解决回调地狱

// 方法1:拆分函数
function getAuthorName(userId, callback) {
    getUser(userId, function(user) {
        getPosts(user.id, function(posts) {
            getComments(posts[0].id, function(comments) {
                getAuthor(comments[0].authorId, function(author) {
                    callback(null, author.name);
                });
            });
        });
    });
}

// 方法2:命名函数
function step1(callback) {
    getUser(userId, callback);
}

function step2(user, callback) {
    getPosts(user.id, callback);
}

function step3(posts, callback) {
    getComments(posts[0].id, callback);
}

function step4(comments, callback) {
    getAuthor(comments[0].authorId, callback);
}

step1(function(user) {
    step2(user, function(posts) {
        step3(posts, function(comments) {
            step4(comments, function(author) {
                console.log(author.name);
            });
        });
    });
});

// 方法3:使用Promise(推荐)
getUser(userId)
    .then(user => getPosts(user.id))
    .then(posts => getComments(posts[0].id))
    .then(comments => getAuthor(comments[0].authorId))
    .then(author => console.log(author.name))
    .catch(error => console.error(error));

// 方法4:使用async/await(最佳)
async function getAuthorName(userId) {
    try {
        const user = await getUser(userId);
        const posts = await getPosts(user.id);
        const comments = await getComments(posts[0].id);
        const author = await getAuthor(comments[0].authorId);
        return author.name;
    } catch (error) {
        console.error(error);
    }
}

错误优先回调

Node.js中的回调约定。

错误优先模式

// 错误优先回调:第一个参数是错误,第二个是结果
function readFile(path, callback) {
    fs.readFile(path, "utf8", function(err, data) {
        if (err) {
            callback(err);
            return;
        }
        
        try {
            const result = JSON.parse(data);
            callback(null, result);
        } catch (e) {
            callback(e);
        }
    });
}

// 使用
readFile("config.json", function(err, data) {
    if (err) {
        console.error("读取失败:", err);
        return;
    }
    console.log("数据:", data);
});

错误优先约定

// 约定:
// 1. 回调函数是最后一个参数
// 2. 错误是回调函数的第一个参数
// 3. 错误为null表示成功

// 正确示例
function asyncOperation(options, callback) {
    if (!options) {
        callback(new Error("options is required"));
        return;
    }
    
    // 异步操作
    setTimeout(function() {
        callback(null, { success: true });
    }, 1000);
}

// 使用
asyncOperation({ data: "东巴文" }, function(err, result) {
    if (err) {
        console.error(err);
        return;
    }
    console.log(result);
});

回调转Promise

// 将错误优先回调转为Promise
function promisify(fn) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            fn.call(this, ...args, (err, result) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(result);
                }
            });
        });
    };
}

// 使用
const readFileAsync = promisify(fs.readFile);

async function readConfig() {
    try {
        const data = await readFileAsync("config.json", "utf8");
        return JSON.parse(data);
    } catch (err) {
        console.error(err);
    }
}

// Node.js内置promisify
const { promisify } = require("util");
const readFileAsync = promisify(fs.readFile);

下一步

掌握了回调函数后,让我们继续学习:

  1. Promise - 学习Promise异步处理
  2. Async/Await - 学习async/await语法
  3. 异步迭代 - 学习异步迭代器

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:回调函数是JavaScript异步编程的基础,但多层嵌套会导致回调地狱。理解回调模式有助于更好地使用Promise和async/await。在 db-w.cn,我们帮你掌握异步编程的精髓!