Skip to content

1.5 异步编程

JavaScript 是单线程语言,异步编程是处理耗时操作(如网络请求、文件读取)的核心机制。

一、同步与异步

同步执行

javascript
console.log("1");
console.log("2");
console.log("3");
// 输出:1, 2, 3(按顺序执行)

异步执行

javascript
console.log("1");

setTimeout(() => {
    console.log("2");
}, 0);

console.log("3");
// 输出:1, 3, 2(setTimeout 是异步的,放入任务队列)

事件循环

┌─────────────────────┐
│   执行栈(同步代码)   │
└──────────┬──────────┘

           ↓ 完成后
┌─────────────────────┐
│  任务队列(异步代码)  │ ← 浏览器 API 调用后放入
└─────────────────────┘

           ↓ 栈空闲时
           执行任务队列中的任务

二、回调函数(Callback)

基本用法

javascript
function getData(callback) {
    setTimeout(() => {
        const data = { name: "张三", age: 25 };
        callback(data);
    }, 1000);
}

function handleData(data) {
    console.log(data);
}

getData(handleData);

回调地狱

javascript
// 层层嵌套,难以维护
getData1(data1 => {
    getData2(data2 => {
        getData3(data3 => {
            getData4(data4 => {
                // ... 越来越深
            });
        });
    });
});

错误处理

javascript
function getData(successCallback, errorCallback) {
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            successCallback({ name: "张三" });
        } else {
            errorCallback("获取数据失败");
        }
    }, 1000);
}

getData(
    data => console.log(data),
    error => console.error(error)
);

三、Promise(ES6)

基本用法

javascript
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            resolve({ name: "张三" });
        } else {
            reject("失败");
        }
    }, 1000);
});

// 使用
promise
    .then(data => {
        console.log("成功:", data);
    })
    .catch(error => {
        console.error("失败:", error);
    })
    .finally(() => {
        console.log("无论如何都会执行");
    });

链式调用

javascript
getData1()
    .then(data1 => {
        console.log(data1);
        return getData2(); // 返回新的 Promise
    })
    .then(data2 => {
        console.log(data2);
        return getData3();
    })
    .then(data3 => {
        console.log(data3);
    })
    .catch(error => {
        console.error(error);
    });

Promise 静态方法

javascript
// Promise.all(全部成功才成功)
Promise.all([promise1, promise2, promise3])
    .then(([result1, result2, result3]) => {
        console.log("全部成功");
    });

// Promise.race(第一个完成的结果)
Promise.race([promise1, promise2])
    .then(result => {
        console.log("最快的:", result);
    });

// Promise.allSettled(全部完成,无论成功失败)
Promise.allSettled([promise1, promise2])
    .then(results => {
        results.forEach(result => {
            if (result.status === "fulfilled") {
                console.log("成功:", result.value);
            } else {
                console.log("失败:", result.reason);
            }
        });
    });

// Promise.any(第一个成功的)
Promise.any([promise1, promise2])
    .then(result => {
        console.log("第一个成功的:", result);
    });

四、async/await(ES2017)

基本用法

javascript
async function fetchData() {
    try {
        const data = await getData();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

fetchData();

多个异步操作

javascript
// 顺序执行(串行)
async function sequential() {
    const data1 = await getData1();
    const data2 = await getData2(data1);
    const data3 = await getData3(data2);
}

// 并行执行(推荐)
async function parallel() {
    const [data1, data2] = await Promise.all([
        getData1(),
        getData2()
    ]);
}

错误处理

javascript
// try-catch
async function handleData() {
    try {
        const data = await getData();
        return data;
    } catch (error) {
        console.error(error);
        return null; // 返回默认值
    }
}

// Promise.catch()
getData()
    .then(data => console.log(data))
    .catch(error => console.error(error));

五、常见异步场景

1. 网络请求(Fetch API)

javascript
// Promise 方式
fetch("https://api.example.com/data")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

// async/await 方式
async function fetchData() {
    try {
        const response = await fetch("https://api.example.com/data");
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

2. 定时器

javascript
// setTimeout(延迟执行)
setTimeout(() => {
    console.log("延迟执行");
}, 1000);

// setInterval(定时执行)
const timer = setInterval(() => {
    console.log("定时执行");
}, 1000);

// 清除定时器
clearInterval(timer);

// Promise 封装定时器
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function example() {
    console.log("开始");
    await delay(1000);
    console.log("1秒后");
}

3. 事件监听

javascript
// 监听按钮点击
button.addEventListener("click", () => {
    console.log("点击了按钮");
});

// 等待事件(封装为 Promise)
function waitForClick(button) {
    return new Promise(resolve => {
        button.addEventListener("click", resolve);
    });
}

async function handleClick() {
    await waitForClick(button);
    console.log("按钮被点击");
}

六、最佳实践

1. 避免回调地狱

javascript
// 不好(回调地狱)
doSomething(result1 => {
    doSomethingElse(result2 => {
        doThirdThing(result3 => {
            doFinalThing(result4);
        });
    });
});

// 好(Promise 链式调用)
doSomething()
    .then(result1 => doSomethingElse(result1))
    .then(result2 => doThirdThing(result2))
    .then(result3 => doFinalThing(result3))
    .catch(error => console.error(error));

// 更好(async/await)
async function doAll() {
    try {
        const result1 = await doSomething();
        const result2 = await doSomethingElse(result1);
        const result3 = await doThirdThing(result2);
        await doFinalThing(result3);
    } catch (error) {
        console.error(error);
    }
}

2. 错误处理

javascript
// 每个 Promise 都应该有错误处理
async function handleData() {
    try {
        const data = await getData(); // 可能出错
        const processed = await processData(data); // 可能出错
        return processed;
    } catch (error) {
        // 统一错误处理
        console.error("操作失败:", error);
        throw error; // 继续抛出或处理
    }
}

3. 并行请求优化

javascript
// 不好(串行,速度慢)
async function fetchData() {
    const user = await fetchUser();
    const posts = await fetchPosts();
    const comments = await fetchComments();
}

// 好(并行,速度快)
async function fetchData() {
    const [user, posts, comments] = await Promise.all([
        fetchUser(),
        fetchPosts(),
        fetchComments()
    ]);
}

4. 超时控制

javascript
// 设置超时
async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => {
        controller.abort();
    }, timeout);

    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        return await response.json();
    } catch (error) {
        if (error.name === "AbortError") {
            throw new Error("请求超时");
        }
        throw error;
    } finally {
        clearTimeout(timeoutId);
    }
}

七、总结

  • JavaScript 是单线程的,异步编程通过事件循环实现
  • 回调函数容易产生回调地狱
  • Promise 解决了回调地狱问题
  • async/await 让异步代码看起来像同步代码
  • 合理使用并行处理可以提升性能