JavaScript异步编程

异步编程是一种编程模式,允许代码在等待某些操作完成的同时继续执行其他任务,不会阻塞主线程。

同步与异步的区别:

  • 同步:按顺序执行,前一个未完成,后一个无法开始;
  • 异步:代码可以并行。

一、异步编程原理

执行模型:

  • 单线程:只有一个主线程,负责执行代码;
  • 事件循环:管理任务队列的执行机制;
  • 任务队列:宏任务队列和微任务队列。

异步编程作用:

  • 避免阻塞ui:防止长时间运行冻结页面;
  • 提高性能;
  • 改善用户体验;
  • 处理i/o密集型操作:网络请求,文件读写,数据库操作等。

二、异步编程的实现方法

1、回调函数

回调函数是作为参数传递给另一个函数的函数,在特定条件满足或事件发生时被调用。

(1)回调函数的本质

回调函数体现了函数可以:

  • 被赋值给变量
  • 作为参数传递
  • 作为返回值
  • 存储在数据结构中

(2)回调函数的类型

a、同步回调
javascript 复制代码
// 同步回调示例
function processArray(arr, processor) {
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        // 同步执行回调
        result.push(processor(arr[i]));
    }
    return result;
}

const numbers = [1, 2, 3, 4, 5];
const doubled = processArray(numbers, (num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// JavaScript内置的同步回调方法
const fruits = ['apple', 'banana', 'cherry'];
fruits.forEach((fruit, index) => {
    console.log(`${index}: ${fruit}`);
});
// 输出:
// 0: apple
// 1: banana
// 2: cherry
b、异步回调
javascript 复制代码
// 异步回调示例
function fetchData(url, onSuccess, onError) {
    console.log(`开始从 ${url} 获取数据...`);
    
    // 模拟异步操作
    setTimeout(() => {
        const success = Math.random() > 0.3;
        
        if (success) {
            const data = { url, timestamp: Date.now() };
            onSuccess(data); // 成功回调
        } else {
            onError(new Error(`从 ${url} 获取数据失败`)); // 错误回调
        }
    }, 1000);
}

// 使用异步回调
fetchData(
    'https://api.example.com/users',
    (data) => {
        console.log('数据获取成功:', data);
    },
    (error) => {
        console.error('发生错误:', error.message);
    }
);

(3)回调函数的部分应用示例

数组中的回调;

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// map: 转换数组
const squared = numbers.map((num) => num * num);
console.log('平方:', squared); // [1, 4, 9, 16, 25]

// filter: 过滤数组
const evenNumbers = numbers.filter((num) => num % 2 === 0);
console.log('偶数:', evenNumbers); // [2, 4]

// reduce: 累积计算
const sum = numbers.reduce((accumulator, current) => {
    return accumulator + current;
}, 0);
console.log('总和:', sum); // 15

// sort: 排序
const unsorted = [3, 1, 4, 1, 5, 9, 2];
const sorted = unsorted.sort((a, b) => a - b);
console.log('排序后:', sorted); // [1, 1, 2, 3, 4, 5, 9]

// find: 查找元素
const firstEven = numbers.find((num) => num % 2 === 0);
console.log('第一个偶数:', firstEven); // 2

事件处理中的回调:

javascript 复制代码
// DOM事件回调
document.addEventListener('DOMContentLoaded', () => {
    console.log('DOM已加载完成');
    
    const button = document.getElementById('myButton');
    if (button) {
        // 添加点击事件回调
        button.addEventListener('click', (event) => {
            console.log('按钮被点击', event);
            alert('按钮被点击!');
        });
        
        // 添加鼠标悬停事件回调
        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#f0f0f0';
        });
        
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '';
        });
    }
});

// 创建自定义事件处理器
class EventHandler {
    constructor() {
        this.events = {};
    }
    
    // 注册事件回调
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    }
    
    // 触发事件,执行所有回调
    emit(eventName, data) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(callback => {
                try {
                    callback(data);
                } catch (error) {
                    console.error(`事件 ${eventName} 的回调执行失败:`, error);
                }
            });
        }
    }
    
    // 移除事件回调
    off(eventName, callbackToRemove) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(
                callback => callback !== callbackToRemove
            );
        }
    }
}

// 使用自定义事件处理器
const handler = new EventHandler();

const userLoggedIn = (user) => {
    console.log(`用户 ${user.name} 已登录`);
};

handler.on('login', userLoggedIn);
handler.on('login', (user) => {
    console.log(`发送登录通知给 ${user.email}`);
});

handler.emit('login', { name: '张三', email: 'zhangsan@example.com' });

// 移除特定回调
handler.off('login', userLoggedIn);

实现防抖和节流:

javascript 复制代码
// 防抖:在事件频繁触发时,只执行最后一次
function debounce(fn, delay) {
    let timer = null;
    
    return function(...args) {
        // 清除之前的定时器
        clearTimeout(timer);
        
        // 设置新的定时器
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

// 节流:在事件频繁触发时,每隔一段时间执行一次
function throttle(fn, interval) {
    let lastTime = 0;
    let timer = null;
    
    return function(...args) {
        const now = Date.now();
        const remainingTime = interval - (now - lastTime);
        
        if (remainingTime <= 0) {
            // 距离上次执行已经超过间隔时间
            lastTime = now;
            fn.apply(this, args);
        } else if (!timer) {
            // 设置定时器,确保最后一次也会执行
            timer = setTimeout(() => {
                lastTime = Date.now();
                timer = null;
                fn.apply(this, args);
            }, remainingTime);
        }
    };
}

// 使用示例
const searchInput = document.getElementById('search');
const logMessage = debounce((message) => {
    console.log('搜索关键词:', message);
    // 这里可以执行实际的搜索逻辑
}, 300);

searchInput.addEventListener('input', (event) => {
    logMessage(event.target.value);
});

// 窗口滚动节流
const handleScroll = throttle(() => {
    console.log('滚动位置:', window.scrollY);
    // 这里可以执行滚动相关的逻辑
}, 200);

window.addEventListener('scroll', handleScroll);

(4)回调地狱

指多层嵌套难以阅读和维护。

javascript 复制代码
// 典型的回调地狱示例
getUser(1, (error, user) => {
    if (error) {
        console.error('获取用户失败:', error);
    } else {
        getOrders(user.id, (error, orders) => {
            if (error) {
                console.error('获取订单失败:', error);
            } else {
                getOrderDetails(orders[0].id, (error, details) => {
                    if (error) {
                        console.error('获取订单详情失败:', error);
                    } else {
                        updateInventory(details.productId, (error, result) => {
                            if (error) {
                                console.error('更新库存失败:', error);
                            } else {
                                console.log('所有操作完成');
                            }
                        });
                    }
                });
            }
        });
    }
});

解决方案:

  • 命名为函数(减少嵌套);
  • 使用async/await;
  • 使用promise链;

2、Promise

promise是JavaScript中处理异步操作的对象,他代表一个异步操作的最终完成或失败及其结果值。(举个例子:当你发起一个异步任务,JavaScript会给你一个promise对象,此时这个对象就像一个空盒子/提货单,他代表一个承诺未来的某个时刻,这个盒子中装着你想要的结果,也可能是一个失败的原因)。

特点:

  • 管理异步:专门用来处理网络请求,定时器,读写文件等;
  • 避免回调地狱:promise提供链式调用的方式根据promise提供的三种状态去书写异步逻辑;
  • 统一处理错误:通过.catch()可以在链式调用的末尾统一捕获和处理整个链条任何错误;
  • 协调多个异步任务:promise.all等所有任务都成功,promis.race只看谁最快完成。

(1)promise三状态

  • Pending(待定状态):初始状态,及没有被兑现也没有被拒绝;
  • Fulfilled(已兑现状态):操作成功完成;
  • Rejected(已拒绝状态):操作失败。

(2)promise工作原理

promise使用微任务队列,优先级高于宏任务。(就是说在宏任务(主进程)运行结束后每次都会先去检查微任务列表是否为空,若不为空,则优先响应微任务,直到其空为止才去执行下一个宏任务;在执行宏任务期间若是来了新的微任务则不会响应,只有在宏任务完成之后才会响应)。

javascript 复制代码
console.log('1. 开始');

setTimeout(() => {
    console.log('6. setTimeout (宏任务)');
}, 0);

Promise.resolve()
    .then(() => {
        console.log('3. Promise 1 (微任务)');
        return '结果';
    })
    .then((result) => {
        console.log('4. Promise 2:', result);
    });

Promise.resolve().then(() => {
    console.log('5. Promise 3 (微任务)');
});

console.log('2. 结束');

// 输出顺序:
// 1. 开始
// 2. 结束
// 3. Promise 1 (微任务)
// 5. Promise 3 (微任务)
// 4. Promise 2: 结果
// 6. setTimeout (宏任务)

4在5之后的原因:微任务队列是先进先出,由于两个promise是同步代码执行,而4是第一个promise执行完之后产生的新任务,所以他将会被追加到队列末尾。

口诀:同步先,微次之,宏最后;微队列,顺序走,新任务,排后头。

javascript 复制代码
function demonstratePromiseFlow() {
    console.log('1. 创建 Promise');
    
    const promise = new Promise((resolve, reject) => {
        console.log('2. 执行器函数立即执行');
        
        setTimeout(() => {
            console.log('4. 异步操作完成');
            resolve('最终结果');
        }, 100);
    });
    
    console.log('3. Promise 已创建,状态为 pending');
    
    promise.then((result) => {
        console.log('5. then() 被调用:', result);
    });
    
    console.log('6. 同步代码继续执行');
}

demonstratePromiseFlow();
//1. 创建 Promise
//2. 执行器函数立即执行
//3. Promise 已创建,状态为 pending
//6. 同步代码继续执行
//4. 异步操作完成
//5. then() 被调用:最终结果

注:.then()回调不会在注册时立即执行,而是在promise被resolve后才会执行

所以在1、2执行完,setTimeout被调用到宏任务队列100ms后执行;随后执行3宏任务,遇到4时由于当前promise是pending状态所以回调被保存起来,执行6宏任务结束后执行settimeout随后promise状态变为fulfilled触发5.

(3)promise核心方法

a、构造函数
javascript 复制代码
// 创建 Promise
const promise = new Promise((resolve, reject) => {
    // 这个函数被称为 "执行器函数" (executor)
    // 它立即执行
    
    // 模拟异步操作
    const operation = () => {
        try {
            const result = doSomeWork();
            resolve(result); // 成功时调用
        } catch (error) {
            reject(error); // 失败时调用
        }
    };
    
    setTimeout(operation, 1000);
});

// 执行器函数的执行时机
console.log('1. 创建 Promise 前');

const promise2 = new Promise(() => {
    console.log('2. 执行器函数立即执行');
});

console.log('3. Promise 已创建');
b、then()方法
javascript 复制代码
// then() 的基本用法
promise.then(
    // 第一个参数:onFulfilled,成功时的回调
    (value) => {
        console.log('成功:', value);
        return value + ' 已处理';
    },
    // 第二个参数:onRejected,失败时的回调(可选)
    (error) => {
        console.error('失败:', error);
        throw new Error('处理失败');
    }
);

// then() 返回新的 Promise
const promiseChain = Promise.resolve(10)
    .then(value => {
        console.log('第一步:', value);
        return value * 2; // 返回值会成为下一个 then 的参数
    })
    .then(value => {
        console.log('第二步:', value);
        return value + 5;
    })
    .then(value => {
        console.log('第三步:', value);
        return value;
    });

promiseChain.then(finalValue => {
    console.log('最终结果:', finalValue);
});
// 输出:第一步: 10, 第二步: 20, 第三步: 25, 最终结果: 25
c、catch()方法
javascript 复制代码
// catch() 用于错误处理
Promise.reject(new Error('出错了!'))
    .then(value => {
        console.log('这个不会执行');
        return value;
    })
    .catch(error => {
        console.error('捕获到错误:', error.message);
        return '默认值';
    })
    .then(value => {
        console.log('继续执行:', value);
    });

// 多个 catch() 的链式调用
Promise.resolve()
    .then(() => {
        throw new Error('错误1');
    })
    .catch(error => {
        console.log('捕获错误1:', error.message);
        throw new Error('错误2');
    })
    .catch(error => {
        console.log('捕获错误2:', error.message);
        return '恢复执行';
    })
    .then(value => {
        console.log('最终:', value);
    });
d、finally()方法
javascript 复制代码
// finally() 无论成功失败都会执行
function processData(data) {
    console.log('开始处理数据...');
    
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = Math.random() > 0.5;
            if (success) {
                resolve(`处理成功: ${data}`);
            } else {
                reject(new Error(`处理失败: ${data}`));
            }
        }, 1000);
    })
    .finally(() => {
        console.log('清理工作完成');
    });
}

// 使用 finally
processData('测试数据')
    .then(result => console.log('结果:', result))
    .catch(error => console.error('错误:', error.message));
e、静态方法------Promise.resolve()
javascript 复制代码
// 创建已解决的 Promise
const resolvedPromise = Promise.resolve('立即值');

// 等价于
const equivalentPromise = new Promise(resolve => {
    resolve('立即值');
});

// 特殊用法:处理 thenable 对象
const thenable = {
    then: function(resolve, reject) {
        resolve('来自 thenable 的值');
    }
};

Promise.resolve(thenable).then(value => {
    console.log(value); // '来自 thenable 的值'
});

Thenable是任何具有.then()方法的对象

普通resolve和thenable的resolve区别:

  • 普通resolve:直接设置promise的状态和值,立即执行,不会等待;传递什么值promise就有什么值;不涉及任何异步操作。
  • thenable的resolve:先调用thenable.then()方法,状态等该方法执行完后才确定;最终值取决于thenable.ehtn()中调用的resolve;如果thenable.then()中有异步操作会等待完成。

thenable的resolve特点:

  • Promise.resolve会自动展开thenable
  • thenable可以不是真正的Promise(任何拥有then方法的对象都是thenable)
  • 嵌套thenable会被递归展开
f、静态方法------Promise.reject()
javascript 复制代码
// 创建已拒绝的 Promise
const rejectedPromise = Promise.reject(new Error('拒绝原因'));

// 实际应用:快速创建错误响应
function getUser(id) {
    if (!id) {
        return Promise.reject(new Error('ID 不能为空'));
    }
    // 正常的异步操作...
}
g、静态方法------Promise.all()
javascript 复制代码
// 并行执行多个 Promise,所有成功才成功
const promises = [
    fetchData('/api/user/1'),
    fetchData('/api/user/2'),
    fetchData('/api/user/3')
];

Promise.all(promises)
    .then(results => {
        console.log('所有数据获取成功:', results);
        // results: [user1, user2, user3]
    })
    .catch(error => {
        console.error('有一个请求失败:', error);
        // 只要有一个失败,整个 Promise.all 就失败
    });

// 实际示例
const timeout = (ms, value) => 
    new Promise(resolve => setTimeout(() => resolve(value), ms));

Promise.all([
    timeout(100, '第一个'),
    timeout(200, '第二个'),
    timeout(300, '第三个')
]).then(values => {
    console.log('所有完成:', values); // 大约300ms后输出
});
h、静态方法------Promise.allSettled()
javascript 复制代码
// 等待所有 Promise 完成(无论成功或失败)
const promises = [
    Promise.resolve('成功1'),
    Promise.reject('失败1'),
    Promise.resolve('成功2'),
    Promise.reject('失败2')
];

Promise.allSettled(promises)
    .then(results => {
        console.log('所有 Promise 都已处理:');
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index}: 成功`, result.value);
            } else {
                console.log(`Promise ${index}: 失败`, result.reason);
            }
        });
    });
i、静态方法------Promise.race()
javascript 复制代码
// 竞速:第一个完成的 Promise 决定结果
const timeoutPromise = timeout => 
    new Promise((_, reject) => 
        setTimeout(() => reject(new Error('超时')), timeout)
    );

const fetchPromise = fetchData('/api/data');

Promise.race([fetchPromise, timeoutPromise(5000)])
    .then(data => {
        console.log('数据获取成功:', data);
    })
    .catch(error => {
        console.error('请求超时或失败:', error.message);
    });

// 实际应用:最快响应胜出
const fastestAPI = Promise.race([
    fetch('https://api1.example.com/data'),
    fetch('https://api2.example.com/data'),
    fetch('https://api3.example.com/data')
]);
j、静态方法------Promise.any()
javascript 复制代码
// 第一个成功的 Promise 决定结果(忽略失败的)
const apis = [
    fetch('https://api1.example.com').catch(() => 'api1失败'),
    fetch('https://api2.example.com').catch(() => 'api2失败'),
    fetch('https://api3.example.com')  // 假设这个成功
];

Promise.any(apis)
    .then(firstSuccess => {
        console.log('第一个成功的响应:', firstSuccess);
    })
    .catch(error => {
        console.error('所有请求都失败了:', error);
        // error.aggregateErrors 包含所有失败原因
    });

(4)promise简单应用

网络请求处理

javascript 复制代码
// 使用 Promise 封装 XMLHttpRequest
function httpRequest(method, url, data = null) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.setRequestHeader('Content-Type', 'application/json');
        
        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    const response = JSON.parse(xhr.responseText);
                    resolve(response);
                } catch (error) {
                    resolve(xhr.responseText);
                }
            } else {
                reject(new Error(`请求失败: ${xhr.status}`));
            }
        };
        
        xhr.onerror = () => {
            reject(new Error('网络错误'));
        };
        
        xhr.onabort = () => {
            reject(new Error('请求被取消'));
        };
        
        xhr.send(data ? JSON.stringify(data) : null);
    });
}

// 使用示例
httpRequest('GET', 'https://api.example.com/users')
    .then(users => {
        console.log('获取用户列表:', users);
        return httpRequest('POST', 'https://api.example.com/users', {
            name: '新用户'
        });
    })
    .then(newUser => {
        console.log('创建新用户成功:', newUser);
    })
    .catch(error => {
        console.error('请求出错:', error.message);
    });

异步文件处理

javascript 复制代码
// 模拟异步文件操作
const fileSystem = {
    readFile: (filename) => {
        return new Promise((resolve, reject) => {
            console.log(`开始读取 ${filename}...`);
            setTimeout(() => {
                const success = Math.random() > 0.2;
                if (success) {
                    resolve(`文件 ${filename} 的内容`);
                } else {
                    reject(new Error(`读取 ${filename} 失败`));
                }
            }, Math.random() * 1000);
        });
    },
    
    writeFile: (filename, content) => {
        return new Promise((resolve, reject) => {
            console.log(`开始写入 ${filename}...`);
            setTimeout(() => {
                resolve(`文件 ${filename} 写入成功`);
            }, Math.random() * 1000);
        });
    }
};

// 顺序处理多个文件
async function processFilesSequentially() {
    try {
        const file1 = await fileSystem.readFile('file1.txt');
        console.log('文件1:', file1);
        
        const file2 = await fileSystem.readFile('file2.txt');
        console.log('文件2:', file2);
        
        const result = await fileSystem.writeFile('output.txt', file1 + '\n' + file2);
        console.log(result);
    } catch (error) {
        console.error('文件处理失败:', error.message);
    }
}

// 并行处理多个文件
function processFilesInParallel() {
    const filePromises = [
        fileSystem.readFile('file1.txt'),
        fileSystem.readFile('file2.txt'),
        fileSystem.readFile('file3.txt')
    ];
    
    return Promise.all(filePromises)
        .then(contents => {
            console.log('所有文件读取成功:', contents);
            const combined = contents.join('\n---\n');
            return fileSystem.writeFile('combined.txt', combined);
        })
        .then(result => {
            console.log('合并文件成功:', result);
        })
        .catch(error => {
            console.error('文件操作失败:', error.message);
        });
}

3、Async/Await

让基于promise的异步代码看上去更接近于同步代码。

  • async:用于声明一个函数,并且该函数总返回一个promise(如果该函数本身返回一个非promise,则async会自动将这个值用Promise.resolve包装成一个成功的promise,如果函数内部出错则会返回一个被拒绝状态的promise);
  • await:只能用于async声明的函数内部(await后面总是跟一个promise或thenable对象);用于暂停所在async函数的执行,等待后面promise完成(在等待期间,JavaScript引擎可以离开这个函数去执行其他代码)。

注:在使用async与await时可以用try...catch进行捕获(纯promise不行)

javascript 复制代码
// 对比:Promise vs async/await

// 使用 Promise
function fetchDataWithPromise() {
    return fetch('/api/data')
        .then(response => response.json())
        .then(data => processData(data))
        .catch(error => console.error(error));
}

// 使用 async/await
async function fetchDataWithAsync() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return processData(data);
    } catch (error) {
        console.error(error);
    }
}

(1)async将函数包装成promise

(2)async/await执行流程

javascript 复制代码
async function demonstration() {
    console.log('1. 开始执行');
    
    // await 会暂停函数执行,但不会阻塞主线程
    const data = await fetchData(); // 假设返回 Promise
    
    console.log('3. 获取到数据:', data);
    return data;
}

console.log('0. 调用 async 函数');
const promise = demonstration();
console.log('2. async 函数立即返回 Promise:', promise);
promise.then(data => console.log('4. 最终结果:', data));

// 输出顺序:
// 0. 调用 async 函数
// 1. 开始执行
// 2. async 函数立即返回 Promise: Promise { <pending> }
// 3. 获取到数据: ...
// 4. 最终结果: ...

(3)核心特性

javascript 复制代码
//串行执行
// 多个异步操作按顺序执行
async function processUser(userId) {
    // 1. 获取用户信息
    const user = await getUser(userId);
    console.log('用户:', user.name);
    
    // 2. 获取用户订单(等待上一步完成)
    const orders = await getOrders(user.id);
    console.log('订单数:', orders.length);
    
    // 3. 获取订单详情(等待上一步完成)
    const orderDetails = await getOrderDetails(orders[0].id);
    console.log('订单详情:', orderDetails);
    
    return { user, orders, orderDetails };
}





//并行执行
// 使用 Promise.all 并行执行
async function fetchAllData() {
    // 同时发起所有请求
    const [user, orders, settings] = await Promise.all([
        getUser(1),
        getOrders(1),
        getSettings(1)
    ]);
    
    return { user, orders, settings };
}

// 使用循环处理多个并行请求
async function processMultipleItems(items) {
    //错误:顺序执行,效率低
    for (const item of items) {
        await processItem(item); // 每个都等待
    }
    
    //正确:并行执行
    const promises = items.map(item => processItem(item));
    const results = await Promise.all(promises);
    
    //正确:控制并发数量
    await processWithConcurrency(items, 3);
}

(4)实际应用

javascript 复制代码
// 封装 API 请求
class ApiClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const defaultOptions = {
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.getToken()}`
            }
        };
        
        try {
            const response = await fetch(url, {
                ...defaultOptions,
                ...options
            });
            
            // 处理 HTTP 状态码
            if (response.status === 401) {
                // token 过期,尝试刷新
                await this.refreshToken();
                return this.request(endpoint, options);
            }
            
            if (!response.ok) {
                throw new Error(`请求失败: ${response.status}`);
            }
            
            return await response.json();
        } catch (error) {
            console.error(`API请求失败 ${endpoint}:`, error);
            throw error;
        }
    }
    
    async get(endpoint, params = {}) {
        const queryString = new URLSearchParams(params).toString();
        const url = queryString ? `${endpoint}?${queryString}` : endpoint;
        return this.request(url);
    }
    
    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    // 其他方法:put、delete、patch 等
}

// 使用示例
const api = new ApiClient('https://api.example.com');

async function fetchUserData() {
    try {
        const [user, orders, notifications] = await Promise.all([
            api.get('/users/123'),
            api.get('/users/123/orders'),
            api.get('/users/123/notifications')
        ]);
        
        return { user, orders, notifications };
    } catch (error) {
        // 统一错误处理
        showErrorToast('获取用户数据失败');
        throw error;
    }
}

表单提交处理

javascript 复制代码
// 表单提交优化
async function handleSubmit(event) {
    event.preventDefault();
    
    const form = event.target;
    const submitButton = form.querySelector('button[type="submit"]');
    const originalText = submitButton.textContent;
    
    try {
        // 禁用提交按钮,防止重复提交
        submitButton.disabled = true;
        submitButton.textContent = '提交中...';
        
        // 收集表单数据
        const formData = new FormData(form);
        const data = Object.fromEntries(formData);
        
        // 验证数据
        if (!validateFormData(data)) {
            throw new Error('表单验证失败');
        }
        
        // 提交数据
        const result = await submitFormData(data);
        
        // 显示成功消息
        showSuccessMessage('提交成功!');
        
        // 重置表单或跳转
        form.reset();
        setTimeout(() => {
            window.location.href = '/success';
        }, 2000);
        
    } catch (error) {
        // 显示错误信息
        showErrorMessage(error.message);
        
        // 记录错误
        console.error('表单提交失败:', error);
        trackError(error);
        
    } finally {
        // 恢复按钮状态
        submitButton.disabled = false;
        submitButton.textContent = originalText;
    }
}

// 防抖的搜索功能
function createDebouncedSearch() {
    let timeoutId;
    
    return async function search(query) {
        clearTimeout(timeoutId);
        
        return new Promise(resolve => {
            timeoutId = setTimeout(async () => {
                if (query.trim().length < 2) {
                    resolve([]);
                    return;
                }
                
                try {
                    const results = await searchAPI(query);
                    resolve(results);
                } catch (error) {
                    console.error('搜索失败:', error);
                    resolve([]); // 失败时返回空数组
                }
            }, 300); // 防抖延迟300ms
        });
    };
}

const debouncedSearch = createDebouncedSearch();

// 输入框事件监听
searchInput.addEventListener('input', async (event) => {
    const query = event.target.value;
    const results = await debouncedSearch(query);
    displaySearchResults(results);
});

4、事件监听器(单独学习)

5、Generator函数与yield(单独学习)

相关推荐
Trae1ounG2 小时前
Vue生命周期
前端·javascript·vue.js
—Qeyser3 小时前
Flutter Text 文本组件完全指南
开发语言·javascript·flutter
程序员小李白3 小时前
js数据类型详细解析
前端·javascript·vue.js
weixin_462446233 小时前
Python用Flask后端解析Excel图表,Vue3+ECharts前端动态还原(附全套代码)
前端·python·flask·echats
Kratzdisteln3 小时前
【1902】0120-3 Dify变量引用只能引用一层
android·java·javascript
满栀5853 小时前
jQuery 递归渲染多级树形菜单
前端·javascript·jquery
闲蛋小超人笑嘻嘻3 小时前
Flexbox 属性总结
前端·css
TOPGUS3 小时前
谷歌将移除部分搜索功能:面对AI时代的一次功能精简策略
前端·人工智能·搜索引擎·aigc·seo·数字营销