JavaScript异步编程: 回调callback , Promise,then,catch,finally , async,await 笔记250810

31 阅读35分钟

JavaScript异步编程: 回调callback , Promise,then,catch,finally , async,await 笔记250810

JavaScript 异步编程全面解析:回调、Promise、async/await

一、异步编程基础

     为什么需要异步?

  • 单线程限制:JavaScript 是单线程语言
  • 避免阻塞:防止耗时操作冻结界面
  • 性能优化:充分利用 CPU 和 I/O 资源
  • 用户体验:保持应用响应灵敏

     同步 vs 异步

// 同步 - 阻塞执行
console.log("开始");
const result = loadDataSync(); // 阻塞主线程
console.log(result);
console.log("结束");

// 异步 - 非阻塞
console.log("开始");
loadDataAsync(result => console.log(result)); // 回调函数
console.log("结束"); // 立即执行

二、回调函数(Callback)

     基本模式

function fetchData(callback) {
  setTimeout(() => {
    callback('数据加载完成');
  }, 1000);
}

fetchData(result => {
  console.log(result);
});

     回调地狱问题

getUser(userId, user => {
  getProfile(user.id, profile => {
    getPosts(profile.id, posts => {
      renderPosts(posts, () => {
        notifyUser(() => {
          // 更多嵌套...
        });
      });
    });
  });
});

     解决方案

  1. 命名函数:拆分回调为独立函数
  2. 模块化:功能拆分为独立模块
  3. Promise:更先进的异步管理

三、Promise 核心机制

     创建 Promise

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    Math.random() > 0.5 ? 
      resolve('成功') : 
      reject(new Error('失败'));
  }, 1000);
});

     Promise 状态机

stateDiagram-v2
    [*] --> Pending
    Pending --> Fulfilled: resolve()
    Pending --> Rejected: reject()
    Fulfilled --> [*]
    Rejected --> [*]

     .then() - 处理成功/失败

promise.then(
  value => console.log("成功:", value),
  error => console.error("失败:", error)
);

     .catch() - 错误处理

fetchData()
  .then(processData)
  .catch(error => {
    console.error("请求失败:", error);
    return fallbackData;
  });

     .finally() - 最终清理

let loading = true;

fetchData()
  .then(render)
  .catch(showError)
  .finally(() => {
    loading = false; // 无论成败都执行
    cleanResources();
  });

     链式调用

getUser()
  .then(user => getPosts(user.id))
  .then(posts => filterPosts(posts))
  .then(filtered => render(filtered))
  .catch(handleError);

     静态方法

方法描述示例
Promise.all()所有成功时返回结果Promise.all([p1, p2])
Promise.race()返回第一个完成的结果Promise.race([p1, p2])
Promise.any()返回第一个成功的结果Promise.any([p1, p2])
Promise.allSettled()所有完成后返回结果Promise.allSettled([p1, p2])

四、async/await 革命

     基本用法

async function fetchUserData() {
  try {
    const user = await getUser();
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error("获取失败:", error);
    return null;
  } finally {
    console.log("请求完成");
  }
}

     执行机制

sequenceDiagram
    participant Main as 主线程
    participant Async as async函数
    participant Promise
    
    Main->>Async: 调用异步函数
    Async->>Async: 执行同步代码
    Async->>Promise: await 暂停执行
    Main->>Main: 继续执行其他代码
    Promise-->>Async: Promise 解决
    Async->>Async: 恢复执行
    Async->>Main: 返回结果

     优势

  1. 同步风格:代码线性执行,更易理解
  2. 错误处理:使用 try/catch 统一处理
  3. 变量共享:避免回调的嵌套作用域问题
  4. 调试友好:堆栈追踪更清晰

五、三种方案对比

特性回调函数Promiseasync/await
可读性❌ 差✅ 中✅✅ 优
错误处理❌ 困难✅ 链式.catch✅ try/catch
嵌套问题❌ 回调地狱✅ 链式调用解决✅ 完全解决
返回值❌ 只能回调✅ 链式传递✅ 直接返回
流程控制❌ 复杂✅ Promise.all等✅ 同步风格
调试❌ 困难✅ 一般✅ 优秀
浏览器支持✅ 所有✅ ES6+✅ ES2017+

六、混合使用模式

     Promise 与 async/await 互操作

// Promise 中使用 async 函数
function processData() {
  return fetchData()
    .then(data => transformAsync(data)) // 调用async函数
    .then(result => save(result));
}

async function transformAsync(data) {
  const cleaned = await cleanData(data);
  return enrichData(cleaned);
}

// async 函数中使用 Promise 链
async function complexOperation() {
  const raw = await getRawData();
  return process(raw)
    .then(result => optimize(result))
    .catch(error => fallback());
}

     回调转 Promise

// 将回调式API转换为Promise
function promisify(fn) {
  return (...args) => new Promise((resolve, reject) => {
    fn(...args, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

// 使用示例
const readFileAsync = promisify(fs.readFile);
readFileAsync('file.txt', 'utf8').then(console.log);

七、最佳实践

     1. 错误处理方案

// 方案1: try/catch (推荐)
async function fetchResource() {
  try {
    const data = await fetch('/api');
    return process(data);
  } catch (error) {
    handleError(error);
    return fallback;
  }
}

// 方案2: .catch()
fetch('/api')
  .then(process)
  .catch(handleError);

     2. 并行执行优化

// 顺序执行 (慢)
const user = await getUser();
const posts = await getPosts(user.id);

// 并行执行 (快)
const [user, posts] = await Promise.all([
  getUser(),
  getPosts() // 不依赖user
]);

     3. 循环中的异步处理

// 错误: 顺序执行效率低
async function slowProcess(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// 正确: 并行处理
async function fastProcess(items) {
  await Promise.all(items.map(item => processItem(item)));
}

     4. 避免常见陷阱

// 陷阱1: 忘记await
async function update() {
  const data = fetchData(); // ❌ 忘记await
  save(data); // data是Promise对象
}

// 陷阱2: 不必要的await
async function getData() {
  const data = await fetchData();
  return await process(data); // ❌ 第二个await不必要
}

// 正确写法
async function getData() {
  const data = await fetchData();
  return process(data); // 直接返回Promise
}

八、实际应用场景

     1. 用户登录流程

async function login(username, password) {
  showLoader();
  
  try {
    const user = await authenticate(username, password);
    const profile = await fetchProfile(user.id);
    const preferences = await getPreferences(user.id);
    
    return { user, profile, preferences };
  } catch (error) {
    if (error.code === 'TIMEOUT') {
      return loadCachedProfile();
    }
    throw error;
  } finally {
    hideLoader();
  }
}

     2. 文件分块上传

async function uploadFile(file, onProgress) {
  const CHUNK_SIZE = 1024 * 1024; // 1MB
  const chunks = Math.ceil(file.size / CHUNK_SIZE);
  
  for (let i = 0; i < chunks; i++) {
    const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
    await uploadChunk(chunk, i);
    onProgress(Math.round(((i + 1) / chunks) * 100));
  }
  
  return finalizeUpload(file.name);
}

     3. 数据聚合处理

async function buildDashboard() {
  // 并行启动所有数据请求
  const [salesData, userStats, inventory] = await Promise.all([
    fetchSalesData(),
    getUserStatistics(),
    getInventoryLevels()
  ]);
  
  // 处理数据
  const report = generateReport(salesData, userStats);
  const stockAlert = checkInventory(inventory);
  
  return { report, stockAlert };
}

九、调试技巧

     1. 异步堆栈追踪

async function main() {
  await step1();
}

async function step1() {
  await step2();
}

async function step2() {
  throw new Error('调试信息');
}

main().catch(console.error); // 完整堆栈追踪

     2. Promise 状态检查

const promise = fetchData();

// 检查状态
console.log(promise); // Promise {<pending>}

promise
  .then(() => console.log('成功'))
  .catch(() => console.log('失败'));

     3. 性能分析

console.time('API请求');
const data = await fetch('/api');
console.timeEnd('API请求'); // API请求: 256ms

十、演进历程与未来

     JavaScript 异步演进史

graph LR
    A(回调函数) --> B(Promise)
    B --> C(Generator)
    C --> D(async/await)
    D --> E(异步迭代器)
    E --> F(Web Workers)

     现代异步模式

  1. 异步迭代器for await...of
  2. 顶级 await:模块顶层使用 await
  3. Promise 组合器allSettledany
  4. Web Workers:真正的并行计算

总结

  1. 回调函数:基础异步模式,易导致回调地狱
  2. Promise
    • .then() 处理成功状态
    • .catch() 捕获错误
    • .finally() 最终清理
    • 链式调用解决回调嵌套
  3. async/await
    • 同步风格写异步代码
    • try/catch 错误处理
    • 最佳可读性和可维护性

黄金法则

  • 新项目首选 async/await
  • 旧代码逐步迁移到 Promise/async
  • 简单操作可用 Promise.then
  • 复杂流程用 async/await
  • 始终处理错误(try/catch 或 .catch())

掌握 JavaScript 异步编程,关键在于理解事件循环机制,并灵活选用适合的异步模式。async/await 作为现代 JavaScript 的核心特性,可大幅提升代码质量和开发效率。

















JavaScript 异步编程完全指南:回调、Promise、async/await

一、异步编程的必要性 JavaScript 是单线程语言,异步编程解决了阻塞问题

// 同步阻塞示例
console.log("开始同步操作");
const data = syncFetchData(); // 阻塞执行
console.log("结束同步操作");

// 异步非阻塞示例
console.log("开始异步操作");
asyncFetchData(result => {
    console.log("收到结果:", result);
});
console.log("继续执行其他任务");

二、回调函数 (Callback)      基本模式

function fetchData(callback) {
    setTimeout(() => {
        callback({ id: 1, name: "数据" });
    }, 1000);
}

fetchData(data => {
    console.log("回调结果:", data);
});

     回调地狱问题

getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            renderUI(details, () => {
                // 更多嵌套...
            });
        });
    });
});

     解决方案

  1. 命名函数:避免匿名嵌套
  2. 模块化:拆分功能
  3. 控制流库:如 Async.js

三、Promise 解决方案      创建 Promise

const promise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        Math.random() > 0.5 ? 
            resolve("成功!") : 
            reject(new Error("失败!"));
    }, 1000);
});

     Promise 链式操作

fetch("/api/data")
    .then(response => {
        if (!response.ok) throw new Error("HTTP错误");
        return response.json();
    })
    .then(data => processData(data))
    .then(result => saveResult(result))
    .catch(error => handleError(error))
    .finally(() => cleanup());

     Promise 方法对比

方法描述特点
then()处理成功状态可链式调用
catch()处理错误状态捕获链中所有错误
finally()最终执行不改变结果值
Promise.all()所有成功一个失败即失败
Promise.race()第一个完成不考虑结果
Promise.any()第一个成功全部失败才失败
Promise.allSettled()所有完成包含成功/失败结果

四、async/await 语法糖      基本用法

async function fetchUserData() {
    try {
        const response = await fetch("/api/user");
        const user = await response.json();
        const orders = await fetchUserOrders(user.id);
        return { user, orders };
    } catch (error) {
        console.error("请求失败:", error);
        return null;
    } finally {
        console.log("请求完成");
    }
}

     并行优化

async function loadDashboard() {
    const [user, posts] = await Promise.all([
        fetchUser(),
        fetchPosts()
    ]);
    
    return { user, posts };
}

     错误处理模式

// 方式1: try/catch
async function safeFetch() {
    try {
        return await fetch("/api");
    } catch (error) {
        return null;
    }
}

// 方式2: catch方法
const data = await fetch("/api").catch(() => defaultData);

// 方式3: 结果元组
const [err, data] = await fetch("/api")
    .then(data => [null, data])
    .catch(err => [err, null]);

五、演进关系与转换      技术演进

回调函数 → PromiseGeneratorasync/await

     回调转 Promise

function readFilePromise(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            err ? reject(err) : resolve(data);
        });
    });
}

// 使用
readFilePromise("data.txt")
    .then(console.log)
    .catch(console.error);

     Promise 转 async/await

// Promise链
function process() {
    return step1()
        .then(step2)
        .then(step3);
}

// async/await版
async function process() {
    const result1 = await step1();
    const result2 = await step2(result1);
    return step3(result2);
}

六、异步模式应用场景      1. API请求序列

async function checkoutOrder() {
    const cart = await getCart();
    const address = await getShippingAddress();
    const payment = await processPayment(cart.total);
    await sendConfirmation(cart, address, payment);
}

     2. 错误恢复

async function fetchWithRetry(url, retries = 3) {
    try {
        return await fetch(url);
    } catch (err) {
        if (retries <= 0) throw err;
        await new Promise(r => setTimeout(r, 1000));
        return fetchWithRetry(url, retries - 1);
    }
}

     3. 并发控制

async function runParallel(tasks, concurrency = 5) {
    const results = [];
    const running = [];
    
    for (const task of tasks) {
        const p = task().then(res => {
            running.splice(running.indexOf(p), 1);
            return res;
        });
        
        results.push(p);
        running.push(p);
        
        if (running.length >= concurrency) {
            await Promise.race(running);
        }
    }
    
    return Promise.all(results);
}

七、性能优化与陷阱规避      优化策略

  1. 避免顺序等待

    // 低效
    const a = await fetchA();
    const b = await fetchB();
    
    // 高效
    const [a, b] = await Promise.all([fetchA(), fetchB()]);
    
  2. 缓存Promise

    const userCache = {};
    function getUser(id) {
        if (!userCache[id]) {
            userCache[id] = fetch(`/user/${id}`).then(r => r.json());
        }
        return userCache[id];
    }
    
  3. 流式处理

    async function processLargeFile() {
        const stream = getReadableStream();
        for await (const chunk of stream) {
            await processChunk(chunk);
        }
    }
    

     常见陷阱

陷阱错误示例解决方案
忘记awaitsaveData(data);await saveData(data);
循环中的awaititems.forEach(async i => await p(i));for (const i of items) await p(i);
Promise未处理拒绝fetch('/api');添加catch处理
资源泄漏缺少finally清理使用try/finally

八、现代异步特性      1. 顶层 await (ES2022)

// 模块中直接使用
const config = await fetchConfig();
export default config;

     2. Promise.withResolvers (ES2024)

const { promise, resolve, reject } = Promise.withResolvers();

document.getElementById("btn").onclick = resolve;
await promise;

     3. 异步迭代器

async function* asyncGenerator() {
    yield await fetchPage(1);
    yield await fetchPage(2);
    yield await fetchPage(3);
}

for await (const page of asyncGenerator()) {
    renderPage(page);
}

九、总结与最佳实践      技术选型指南

场景推荐方案
简单异步操作Promise.then/catch
复杂异步流程async/await
并行请求Promise.all
事件处理Promise + 回调
流式数据异步迭代器

     最佳实践清单

  1. 优先使用 async/await:提高可读性
  2. 并行独立操作:使用 Promise.all
  3. 统一错误处理:try/catch 或 catch()
  4. 资源清理:使用 finally 块
  5. 避免阻塞:拆分CPU密集型任务
  6. 命名Promise:提高可读性
  7. 全局错误处理
    // 浏览器
    window.addEventListener("unhandledrejection", e => {
      e.preventDefault();
      console.error("未处理错误:", e.reason);
    });
    
    // Node.js
    process.on("unhandledRejection", (reason) => {
      console.error("未处理拒绝:", reason);
    });
    

     异步编程全景图

graph TD
    A[异步操作] --> B{解决方案}
    B --> C[回调函数]
    B --> D[Promise]
    B --> E[async/await]
    C --> F[事件处理]
    D --> G[链式调用]
    E --> H[同步风格]
    F --> I[简单场景]
    G --> J[复杂流程]
    H --> J
    I --> K[可维护代码]
    J --> K

掌握 JavaScript 异步编程是现代 Web 开发的核心技能。从基础的回调函数到现代 async/await,理解每种模式的适用场景和转换关系,能够帮助开发者编写更健壮、高效的应用程序。随着 ECMAScript 标准的演进,异步编程模式仍在不断发展,建议持续关注新的语言特性。

















Callback回调函数 转 Promise

回调函数转 Promise 完全指南

在 JavaScript 中,将回调函数转换为 Promise 是现代化遗留代码的关键技术。以下是详细的转换方法和最佳实践:

基本转换模式

// 原始回调函数
function callbackStyle(arg, callback) {
  // 异步操作
  setTimeout(() => {
    const result = `处理结果: ${arg}`;
    callback(null, result); // Node.js 风格 (错误优先)
  }, 100);
}

// 转换为 Promise
function promiseStyle(arg) {
  return new Promise((resolve, reject) => {
    callbackStyle(arg, (err, result) => {
      if (err) reject(err);   // 错误时拒绝
      else resolve(result);   // 成功时解决
    });
  });
}

// 使用示例
promiseStyle("输入数据")
  .then(console.log) // 输出: "处理结果: 输入数据"
  .catch(console.error);

Node.js 实用工具方法

Node.js 提供了内置的转换工具:

const util = require('util');

// 转换单个函数
const promiseVersion = util.promisify(callbackStyle);

// 转换整个模块
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);

// 使用
async function readConfig() {
  try {
    const data = await readFileAsync('config.json', 'utf8');
    return JSON.parse(data);
  } catch (err) {
    console.error('读取配置文件失败', err);
    return defaultConfig;
  }
}

浏览器环境实现

浏览器中没有内置的 promisify,可以自己实现:

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      // 添加自定义回调
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}

// 使用示例
const setTimeoutAsync = promisify(setTimeout);

setTimeoutAsync(1000)
  .then(() => console.log('1秒后执行'))
  .catch(console.error);

处理特殊回调模式

     1. 多参数回调

// 原始函数返回多个值
function multiArg(callback) {
  setTimeout(() => callback(null, '结果1', '结果2'), 100);
}

// 转换方法
function promisifyMulti(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, ...results) => {
        if (err) reject(err);
        // 多个结果转为数组
        else resolve(results.length > 1 ? results : results[0]);
      });
    });
  };
}

// 使用
const asyncMulti = promisifyMulti(multiArg);
asyncMulti().then(([res1, res2]) => console.log(res1, res2));

     2. 无错误参数的回调

// jQuery 风格的成功回调
function jqueryStyle(successCallback) {
  setTimeout(() => successCallback('成功结果'), 100);
}

// 转换
function promisifyNoError(fn) {
  return function(...args) {
    return new Promise(resolve => {
      fn(...args, resolve); // 直接使用 resolve
    });
  };
}

     3. 事件发射器转换

// 事件发射器转 Promise
function eventToPromise(emitter, event) {
  return new Promise((resolve, reject) => {
    const successHandler = (...args) => {
      cleanup();
      resolve(args);
    };
    
    const errorHandler = (err) => {
      cleanup();
      reject(err);
    };
    
    function cleanup() {
      emitter.off(event, successHandler);
      emitter.off('error', errorHandler);
    }
    
    emitter.on(event, successHandler);
    emitter.on('error', errorHandler);
  });
}

// 使用示例
const http = require('http');
const server = http.createServer();

async function startServer(port) {
  try {
    server.listen(port);
    await eventToPromise(server, 'listening');
    console.log(`服务器已在端口 ${port} 启动`);
  } catch (err) {
    console.error('启动失败', err);
  }
}

完整转换方案

/**
 * 高级 promisify 函数
 * 支持多种回调模式:
 * 1. Node.js 风格 (err, result)
 * 2. 多返回值 (err, res1, res2)
 * 3. 仅成功回调
 * 4. 自定义上下文
 */
function advancedPromisify(fn, context) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      // 创建自定义回调
      const customCallback = (err, ...results) => {
        if (err) return reject(err);
        
        // 处理不同结果数量
        switch (results.length) {
          case 0: resolve(); break;
          case 1: resolve(results[0]); break;
          default: resolve(results);
        }
      };
      
      // 应用上下文并调用函数
      try {
        fn.apply(context || this, [...args, customCallback]);
      } catch (syncError) {
        reject(syncError);
      }
    });
  };
}

实际应用示例

     1. 文件操作转换

const fs = require('fs');

// 手动转换
const readFile = (path) => new Promise((resolve, reject) => {
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

// 使用
async function processFiles() {
  try {
    const config = await readFile('config.json');
    const template = await readFile('template.html');
    return { config: JSON.parse(config), template };
  } catch (err) {
    console.error('文件处理失败', err);
    throw err;
  }
}

     2. 数据库查询转换

// 旧式数据库接口
const database = {
  query: (sql, callback) => {
    // 模拟异步查询
    setTimeout(() => {
      if (sql.includes('SELECT')) {
        callback(null, [{ id: 1, name: '示例数据' }]);
      } else {
        callback(new Error('无效查询'));
      }
    }, 100);
  }
};

// 转换为 Promise 接口
database.queryAsync = (sql) => new Promise((resolve, reject) => {
  database.query(sql, (err, results) => {
    if (err) reject(err);
    else resolve(results);
  });
});

// 使用
async function getUserData(userId) {
  try {
    const rows = await database.queryAsync(`SELECT * FROM users WHERE id = ${userId}`);
    return rows[0];
  } catch (err) {
    console.error('数据库错误', err);
    return null;
  }
}

最佳实践

  1. 统一错误处理

    // 创建带错误处理的统一封装
    function createSafeAsync(fn) {
      return async (...args) => {
        try {
          return await fn(...args);
        } catch (err) {
          logError(err); // 统一错误日志
          throw err;    // 重新抛出
        }
      };
    }
    
    const safeReadFile = createSafeAsync(readFile);
    
  2. 保持向后兼容

    // 同时提供回调和 Promise 接口
    function dualInterface(arg, callback) {
      if (callback) {
        // 回调模式
        legacyCallbackStyle(arg, callback);
      } else {
        // Promise 模式
        return new Promise((resolve, reject) => {
          legacyCallbackStyle(arg, (err, res) => {
            err ? reject(err) : resolve(res);
          });
        });
      }
    }
    
  3. 性能优化

    // 缓存 promisify 结果
    const promisifyCache = new WeakMap();
    
    function cachedPromisify(fn) {
      if (!promisifyCache.has(fn)) {
        promisifyCache.set(fn, promisify(fn));
      }
      return promisifyCache.get(fn);
    }
    

转换流程图

传统回调函数
      ↓
new Promise 封装
      ↓
添加自定义回调函数
      │
      ├── 成功 → resolve(结果)
      │
      └── 失败 → reject(错误)
            ↓
        .then()/.catch()
            ↓
      async/await 使用

通过以上方法,你可以将任何回调风格的函数转换为 Promise,从而:

  1. 与现代 async/await 语法兼容
  2. 避免回调地狱(Callback Hell)
  3. 使用 try/catch 统一错误处理
  4. 整合到 Promise 链和 Promise.all 等高级模式中
  5. 逐步重构旧代码库而不破坏现有功能

















回调函数转 Promise:完整指南与最佳实践

回调函数与 Promise 的差异

     回调函数模式

function callbackStyle(param, callback) {
  // 异步操作
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      callback(null, `成功结果: ${param}`);
    } else {
      callback(new Error(`失败: ${param}`));
    }
  }, 1000);
}

// 使用回调
callbackStyle('数据', (error, result) => {
  if (error) {
    console.error(error.message);
    return;
  }
  console.log(result);
});

     Promise 模式

function promiseStyle(param) {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      const success = Math.random() > 0.5;
      if (success) {
        resolve(`成功结果: ${param}`);
      } else {
        reject(new Error(`失败: ${param}`));
      }
    }, 1000);
  });
}

// 使用 Promise
promiseStyle('数据')
  .then(result => console.log(result))
  .catch(error => console.error(error.message));

转换策略详解

     1. Node.js 风格回调转换 (错误优先)

// 原始回调函数
function nodeStyleAsync(param, callback) {
  // ...异步操作
  if (error) callback(error);
  else callback(null, result);
}

// 转换为 Promise
function promisifiedNodeStyle(param) {
  return new Promise((resolve, reject) => {
    nodeStyleAsync(param, (error, result) => {
      if (error) reject(error);
      else resolve(result);
    });
  });
}

     2. 成功/失败分离回调转换

// 原始回调函数
function separateCallbacks(param, onSuccess, onError) {
  // ...异步操作
  if (success) onSuccess(result);
  else onError(error);
}

// 转换为 Promise
function promisifiedSeparate(param) {
  return new Promise((resolve, reject) => {
    separateCallbacks(
      param, 
      result => resolve(result), 
      error => reject(error)
    );
  });
}

     3. 带多个参数的回调转换

// 原始回调函数
function multiParamCallback(param, callback) {
  // ...异步操作
  callback(null, data1, data2, data3);
}

// 转换为 Promise
function promisifiedMultiParam(param) {
  return new Promise((resolve) => {
    multiParamCallback(param, (error, ...results) => {
      if (error) reject(error);
      else resolve(results); // 返回数组 [data1, data2, data3]
    });
  });
}

自动转换工具

     1. Node.js util.promisify

const util = require('util');

// Node.js 风格函数
const fs = require('fs');
const readFile = util.promisify(fs.readFile);

// 使用 Promise
async function readConfig() {
  try {
    const data = await readFile('config.json', 'utf8');
    return JSON.parse(data);
  } catch (error) {
    console.error('读取配置失败:', error);
    return {};
  }
}

     2. 自定义 promisify 函数

function promisify(original) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      original.call(this, ...args, (error, ...results) => {
        if (error) return reject(error);
        if (results.length <= 1) resolve(results[0]);
        else resolve(results);
      });
    });
  };
}

// 使用示例
const customReadFile = promisify(fs.readFile);
customReadFile('data.txt', 'utf8')
  .then(data => console.log(data))
  .catch(console.error);

高级转换模式

     1. 回调超时处理

function withTimeout(promise, timeoutMs) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('操作超时')), timeoutMs)
    )
  ]);
}

// 使用
withTimeout(promisifiedFunction(), 3000)
  .then(console.log)
  .catch(error => console.error(error.message));

     2. 重试机制

function withRetry(asyncFn, retries = 3, delay = 1000) {
  return new Promise((resolve, reject) => {
    const attempt = (n) => {
      asyncFn()
        .then(resolve)
        .catch(error => {
          if (n <= 0) return reject(error);
          setTimeout(() => attempt(n - 1), delay);
        });
    };
    attempt(retries);
  });
}

// 使用
withRetry(() => promisifiedUnstableAPI(), 5)
  .then(console.log)
  .catch(console.error);

     3. 批处理回调

function batchPromisify(asyncFn, items) {
  return Promise.all(items.map(item => 
    new Promise((resolve, reject) => {
      asyncFn(item, (error, result) => {
        if (error) reject(error);
        else resolve(result);
      });
    })
  ));
}

// 使用
const items = [1, 2, 3, 4, 5];
batchPromisify(callbackStyleFunction, items)
  .then(results => console.log('批处理结果:', results))
  .catch(console.error);

实际应用场景

     1. 文件操作转换

// 回调版本
const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promise 版本
const { promisify } = require('util');
const readFile = promisify(fs.readFile);

async function readFiles() {
  try {
    const file1 = await readFile('file1.txt', 'utf8');
    const file2 = await readFile('file2.txt', 'utf8');
    console.log(file1, file2);
  } catch (error) {
    console.error('读取文件失败:', error);
  }
}

     2. 数据库操作转换

// MongoDB 回调风格
db.collection('users').findOne({ id: 123 }, (err, user) => {
  if (err) return console.error(err);
  console.log(user);
});

// Promise 版本
function findUser(id) {
  return new Promise((resolve, reject) => {
    db.collection('users').findOne({ id }, (err, user) => {
      if (err) reject(err);
      else resolve(user);
    });
  });
}

// 使用
findUser(123)
  .then(console.log)
  .catch(console.error);

     3. HTTP 请求转换

// 传统 XMLHttpRequest 回调
function xhrRequest(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = () => callback(null, xhr.responseText);
  xhr.onerror = () => callback(new Error('请求失败'));
  xhr.send();
}

// Promise 版本
function fetchUrl(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error('请求失败'));
    xhr.send();
  });
}

// 使用
fetchUrl('/api/data')
  .then(data => console.log(JSON.parse(data)))
  .catch(console.error);

最佳实践与陷阱规避

     1. 避免常见错误

// 错误:未捕获异常
promisifiedFunction().then(console.log); // 缺少 catch

// 正确:始终处理错误
promisifiedFunction()
  .then(console.log)
  .catch(console.error);

// 错误:回调多次调用
function incorrectPromise() {
  return new Promise((resolve, reject) => {
    someAsync((err, data) => {
      if (err) reject(err);
      resolve(data); // 可能同时调用 resolve 和 reject
    });
  });
}

// 正确:确保只调用一次
function correctPromise() {
  return new Promise((resolve, reject) => {
    let resolved = false;
    someAsync((err, data) => {
      if (resolved) return;
      resolved = true;
      
      if (err) reject(err);
      else resolve(data);
    });
  });
}

     2. 上下文绑定

// 回调函数中使用 this
function Database(connection) {
  this.connection = connection;
}

Database.prototype.query = function(sql, callback) {
  // 使用 this.connection 执行查询
  callback(null, `结果: ${sql}`);
};

// 转换时保持上下文
Database.prototype.promisifiedQuery = function(sql) {
  return new Promise((resolve, reject) => {
    this.query(sql, (error, result) => {
      if (error) reject(error);
      else resolve(result);
    });
  });
};

// 使用
const db = new Database('mysql://localhost');
db.promisifiedQuery('SELECT * FROM users')
  .then(console.log)
  .catch(console.error);

     3. 性能优化

// 缓存 promisified 函数
const promisifyCache = new WeakMap();

function smartPromisify(fn) {
  if (promisifyCache.has(fn)) {
    return promisifyCache.get(fn);
  }
  
  const promisified = function(...args) {
    return new Promise((resolve, reject) => {
      fn.call(this, ...args, (error, ...results) => {
        if (error) reject(error);
        else resolve(results.length <= 1 ? results[0] : results);
      });
    });
  };
  
  promisifyCache.set(fn, promisified);
  return promisified;
}

回调转 Promise 总结表

回调模式转换方法示例
错误优先回调检查第一个参数(err, data) => err ? reject(err) : resolve(data)
分离回调分别处理成功/失败onSuccess: resolve, onError: reject
多参数回调返回数组或对象resolve([result1, result2])
上下文绑定使用 .call(this)original.call(this, ...args, callback)
多次调用防护添加调用标志if (called) return; called = true;
批处理操作Promise.all + mapitems.map(item => new Promise(...))

结论与迁移策略

     转换步骤

  1. 识别回调模式:错误优先、分离回调或多参数
  2. 创建 Promise 包装器:使用 new Promise 结构
  3. 处理回调结果:正确调用 resolve/reject
  4. 处理上下文:保持正确的 this 绑定
  5. 添加错误处理:确保所有路径处理错误
  6. 优化性能:考虑批处理和缓存

     迁移建议

  1. 逐步迁移:优先转换关键路径的代码
  2. 混合使用:在过渡期同时支持回调和 Promise
  3. 使用工具函数:如 util.promisify 或自定义实现
  4. 添加类型支持:使用 TypeScript 定义 Promise 返回值
  5. 更新文档:明确标记已转换函数的接口变化
// 兼容模式:同时支持回调和 Promise
function dualInterface(param, callback) {
  // 如果提供了回调,使用回调风格
  if (typeof callback === 'function') {
    asyncOperation(param, callback);
    return;
  }
  
  // 否则返回 Promise
  return new Promise((resolve, reject) => {
    asyncOperation(param, (error, result) => {
      if (error) reject(error);
      else resolve(result);
    });
  });
}

// 使用
dualInterface('data', (err, res) => { /* 回调 */ });
dualInterface('data').then(/* Promise */);

通过掌握这些转换技术和最佳实践,您可以有效地将遗留的回调风格代码迁移到现代化的 Promise 和 async/await 模式,提高代码的可读性、可维护性和错误处理能力。

















JavaScript 回调函数转 Promise 完全指南

在异步编程中,将回调函数转换为 Promise 是提升代码可读性和可维护性的关键技巧。本文将深入探讨回调转 Promise 的各种方法,并提供实际应用示例。

一、回调函数的问题与 Promise 的优势

     回调地狱(Callback Hell)示例

getUser(userId, (user) => {
  getOrders(user.id, (orders) => {
    getOrderDetails(orders[0].id, (details) => {
      renderUI(details, () => {
        // 更多嵌套...
      });
    });
  });
});

     Promise 解决方案

getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(renderUI)
  .catch(handleError);

二、手动转换回调为 Promise

     基本转换模式

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      // 添加回调函数作为最后一个参数
      fn(...args, (error, result) => {
        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      });
    });
  };
}

     使用示例:文件读取

const fs = require('fs');

// 原始回调方式
fs.readFile('data.txt', 'utf8', (err, data) => {
  if (err) console.error(err);
  else console.log(data);
});

// 转换为Promise
const readFilePromise = promisify(fs.readFile);

readFilePromise('data.txt', 'utf8')
  .then(console.log)
  .catch(console.error);

三、Node.js 内置工具

     util.promisify (Node.js 8+)

const util = require('util');
const fs = require('fs');

const readFile = util.promisify(fs.readFile);

async function processFile() {
  try {
    const data = await readFile('config.json', 'utf8');
    return JSON.parse(data);
  } catch (error) {
    console.error('文件处理失败:', error);
    return {};
  }
}

     自定义 promisify 方法

const promisify = (fn) => 
  (...args) => new Promise((resolve, reject) => 
    fn(...args, (err, ...results) => 
      err ? reject(err) : resolve(results.length > 1 ? results : results[0])
    )
  );

四、高级转换技巧

     1. 处理多个返回值

function getUserData(userId, callback) {
  // 模拟返回多个值
  setTimeout(() => callback(null, { name: 'John' }, 30, 'New York'), 100);
}

// 转换
const getUserDataPromise = promisify(getUserData);

getUserDataPromise(123)
  .then(([user, age, city]) => {
    console.log(`${user.name}, ${age}岁, 来自${city}`);
  });

     2. 转换对象方法

const database = {
  connect: (options, callback) => {
    setTimeout(() => callback(null, { status: 'connected' }), 500);
  },
  query: (sql, callback) => {
    setTimeout(() => callback(null, [{ id: 1, name: 'John' }]), 300);
  }
};

// 转换整个对象
function promisifyAll(obj) {
  const result = {};
  for (const key in obj) {
    if (typeof obj[key] === 'function') {
      result[key + 'Async'] = promisify(obj[key]);
    }
  }
  return result;
}

const db = promisifyAll(database);

// 使用
db.connectAsync({ host: 'localhost' })
  .then(() => db.queryAsync('SELECT * FROM users'))
  .then(console.log);

五、浏览器环境中的转换

     1. 转换 XMLHttpRequest

function fetchUrl(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error('请求失败'));
    xhr.send();
  });
}

// 使用
fetchUrl('https://api.example.com/data')
  .then(data => console.log(JSON.parse(data)))
  .catch(console.error);

     2. 转换事件监听器

function eventToPromise(element, eventName) {
  return new Promise(resolve => {
    const handler = (e) => {
      element.removeEventListener(eventName, handler);
      resolve(e);
    };
    element.addEventListener(eventName, handler);
  });
}

// 使用
const button = document.getElementById('submit-btn');

async function handleClick() {
  const event = await eventToPromise(button, 'click');
  console.log('按钮被点击', event);
}

六、实际应用场景

     1. 顺序执行异步操作

// 回调版本
function processData(input, callback) {
  step1(input, (err, result1) => {
    if (err) return callback(err);
    
    step2(result1, (err, result2) => {
      if (err) return callback(err);
      
      step3(result2, callback);
    });
  });
}

// Promise 版本
async function processData(input) {
  const result1 = await step1Promise(input);
  const result2 = await step2Promise(result1);
  return step3Promise(result2);
}

     2. 并行执行异步操作

// 回调版本
function loadAllResources(callback) {
  let count = 0;
  const results = {};
  let hasError = false;
  
  function done(err, name, data) {
    if (hasError) return;
    if (err) {
      hasError = true;
      return callback(err);
    }
    
    results[name] = data;
    if (++count === 3) callback(null, results);
  }
  
  loadResource('users', done);
  loadResource('posts', done);
  loadResource('comments', done);
}

// Promise 版本
async function loadAllResources() {
  const [users, posts, comments] = await Promise.all([
    loadResourcePromise('users'),
    loadResourcePromise('posts'),
    loadResourcePromise('comments')
  ]);
  
  return { users, posts, comments };
}

     3. 复杂工作流控制

async function userCheckout(userId) {
  try {
    const user = await getUser(userId);
    const [cart, address] = await Promise.all([
      getCart(user.id),
      getAddress(user.id)
    ]);
    
    if (cart.items.length === 0) {
      throw new Error('购物车为空');
    }
    
    const payment = await processPayment(user, cart.total);
    await sendConfirmationEmail(user, payment);
    
    return { success: true, orderId: payment.orderId };
  } catch (error) {
    console.error('结账失败:', error);
    return { success: false, error: error.message };
  }
}

七、错误处理最佳实践

     1. 错误类型区分

class DatabaseError extends Error {
  constructor(message) {
    super(message);
    this.name = 'DatabaseError';
  }
}

// 转换时添加错误类型
function promisify(fn) {
  return (...args) => new Promise((resolve, reject) => {
    fn(...args, (error, result) => {
      if (error) {
        reject(new DatabaseError(error.message));
      } else {
        resolve(result);
      }
    });
  });
}

// 使用
try {
  const data = await dbQuery('SELECT * FROM users');
} catch (error) {
  if (error instanceof DatabaseError) {
    console.error('数据库错误:', error);
    // 执行恢复操作
  } else {
    throw error;
  }
}

     2. 错误重试机制

function withRetry(fn, retries = 3, delay = 1000) {
  return async function(...args) {
    let lastError;
    
    for (let i = 0; i < retries; i++) {
      try {
        return await fn(...args);
      } catch (error) {
        lastError = error;
        console.warn(`尝试 ${i+1}/${retries} 失败,${delay}ms后重试`);
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= 2; // 指数退避
      }
    }
    
    throw lastError;
  };
}

// 使用
const reliableFetch = withRetry(promisify(unreliableApiCall), 5);

八、性能优化与注意事项

     1. Promise 内存管理

async function processLargeDataset() {
  const stream = getDataStream();
  let processed = 0;
  
  for await (const chunk of stream) {
    const result = await processChunk(chunk);
    await saveResult(result);
    
    processed++;
    if (processed % 1000 === 0) {
      // 手动触发垃圾回收(仅Node.js)
      if (global.gc) global.gc();
    }
  }
}

     2. 避免 Promise 内存泄漏

// 错误示例:未处理的Promise
function leakyFunction() {
  // 忘记处理返回的Promise
  someAsyncOperation();
}

// 正确做法
async function safeFunction() {
  try {
    await someAsyncOperation();
  } catch (error) {
    console.error('操作失败', error);
  }
}

     3. 回调转 Promise 的局限性

场景问题解决方案
多次回调如 EventEmitter使用事件流或特殊转换
进度报告Promise 不支持进度使用回调+Promise 混合
取消操作原生 Promise 不可取消使用 AbortController
返回多个值resolve 只接受单参数返回对象或数组

九、现代化替代方案

     1. 使用 async/await

// 基于回调的函数
function oldApi(param, callback) { /* ... */ }

// 包装为Promise
const newApi = promisify(oldApi);

// 使用async/await
async function modernUsage() {
  try {
    const result = await newApi('parameter');
    console.log('结果:', result);
  } catch (error) {
    console.error('错误:', error);
  }
}

     2. 使用 async_hooks (Node.js)

const async_hooks = require('async_hooks');
const { promisify } = require('util');

// 创建异步上下文
const asyncContext = new Map();

const hook = async_hooks.createHook({
  init: (asyncId, type, triggerAsyncId) => {
    // 继承父级上下文
    if (asyncContext.has(triggerAsyncId)) {
      asyncContext.set(asyncId, asyncContext.get(triggerAsyncId));
    }
  },
  destroy: (asyncId) => {
    asyncContext.delete(asyncId);
  }
});

hook.enable();

// 增强版promisify
function contextPromisify(fn) {
  return (...args) => {
    const context = { id: Date.now() };
    return new Promise((resolve, reject) => {
      const asyncId = async_hooks.executionAsyncId();
      asyncContext.set(asyncId, context);
      
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve({ result, context });
      });
    });
  };
}

十、总结与最佳实践

     回调转 Promise 的完整流程

graph TD
    A[识别回调函数] --> B{判断回调模式}
    B -->|错误优先| C[使用 promisify]
    B -->|其他模式| D[自定义转换逻辑]
    C --> E[处理多个返回值]
    D --> F[处理特殊场景]
    E --> G[添加错误类型]
    F --> G
    G --> H[测试转换结果]
    H --> I[集成到应用]

     最佳实践清单

  1. 优先使用 util.promisify(Node.js 环境)
  2. 统一错误处理:使用 try/catch 或 .catch()
  3. 保持上下文:使用 bind 或箭头函数
  4. 处理多个返回值:返回对象或数组
  5. 添加超时控制:避免永久挂起
    function withTimeout(promise, ms) {
      return Promise.race([
        promise,
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('超时')), ms)
      ]);
    }
    
  6. 测试转换结果:确保完整功能
  7. 文档化接口:说明返回的 Promise 特性

     回调转 Promise 的价值

  1. 代码可读性:消除回调嵌套
  2. 错误处理:统一使用 try/catch
  3. 流程控制:简化复杂异步逻辑
  4. 现代特性:兼容 async/await 语法
  5. 可维护性:更易重构和扩展

通过将回调函数转换为 Promise,您可以显著提升 JavaScript 代码的质量和可维护性。这一技巧在现代 JavaScript 开发中至关重要,尤其是在处理遗留代码或与旧 API 交互时。

















new Promise().then().catch().finally() , async{await}

JavaScript 异步编程:Promise 链与 async/await 深度解析

核心概念对比

     Promise 链式操作

new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => resolve("初始数据"), 1000);
})
.then(result => {
  console.log("第一步:", result);
  return result + " -> 处理后";
})
.then(result => {
  console.log("第二步:", result);
  return new Promise(resolve => 
    setTimeout(() => resolve(result + " -> 完成"), 500)
  );
})
.catch(error => {
  console.error("捕获错误:", error);
})
.finally(() => {
  console.log("清理操作");
});

     async/await 操作

async function processData() {
  try {
    // 相当于 new Promise()
    let result = await new Promise(resolve => 
      setTimeout(() => resolve("初始数据"), 1000)
    );
    
    console.log("第一步:", result);
    result += " -> 处理后";
    
    console.log("第二步:", result);
    
    // 相当于 .then()
    result = await new Promise(resolve => 
      setTimeout(() => resolve(result + " -> 完成"), 500)
    );
    
    return result;
  } catch (error) {
    console.error("捕获错误:", error);
    throw error;
  } finally {
    console.log("清理操作");
  }
}

processData().then(finalResult => {
  console.log("最终结果:", finalResult);
});

核心概念详解

     1. Promise 构造函数

const promise = new Promise((resolve, reject) => {
  // 执行异步操作
  if (success) {
    resolve(value); // 状态变为 fulfilled
  } else {
    reject(error); // 状态变为 rejected
  }
});

     2. then() 方法

  • 接收两个可选参数:onFulfilledonRejected
  • 返回新 Promise 对象,支持链式调用
promise.then(
  value => {
    // 处理成功状态
    return newValue;
  },
  error => {
    // 处理失败状态(较少使用)
    return recoveryValue;
  }
);

     3. catch() 方法

  • 专门处理 rejected 状态
  • 相当于 .then(null, errorHandler)
promise
  .then(handleSuccess)
  .catch(handleError); // 捕获链中所有错误

     4. finally() 方法

  • 无论成功失败都会执行
  • 不接收任何参数
  • 通常用于清理操作
promise
  .then(handleSuccess)
  .catch(handleError)
  .finally(cleanup); // 总是执行

     5. async 函数

  • 声明异步函数
  • 总是返回 Promise 对象
  • 可以使用 await 关键字
async function fetchData() {
  return "数据"; // 等同于 return Promise.resolve("数据")
}

     6. await 表达式

  • 只能在 async 函数中使用
  • 暂停函数执行,等待 Promise 解决
  • 返回 Promise 的解决值
async function getData() {
  const data = await fetch('/api'); // 等待Promise解决
  return process(data);
}

两种风格转换指南

     1. 基本转换 Promise 链async/await

// Promise 链
function getData() {
  return fetchData()
    .then(process)
    .then(save);
}

// async/await 版本
async function getData() {
  const data = await fetchData();
  const processed = await process(data);
  return await save(processed);
}

     2. 错误处理转换

// Promise 链
function fetchResource() {
  return fetch('/resource')
    .then(handleResponse)
    .catch(error => {
      console.error('请求失败', error);
      return defaultValue;
    });
}

// async/await 版本
async function fetchResource() {
  try {
    const response = await fetch('/resource');
    return await handleResponse(response);
  } catch (error) {
    console.error('请求失败', error);
    return defaultValue;
  }
}

     3. finally 转换

// Promise 链
function transaction() {
  return beginTransaction()
    .then(executeOperation)
    .then(commit)
    .catch(rollback)
    .finally(endTransaction);
}

// async/await 版本
async function transaction() {
  try {
    await beginTransaction();
    const result = await executeOperation();
    await commit();
    return result;
  } catch (error) {
    await rollback();
    throw error;
  } finally {
    await endTransaction();
  }
}

     4. 并行操作转换

// Promise 链
function loadDashboard() {
  return Promise.all([
    fetchUser(),
    fetchOrders(),
    fetchMessages()
  ]).then(([user, orders, messages]) => {
    return { user, orders, messages };
  });
}

// async/await 版本
async function loadDashboard() {
  const [user, orders, messages] = await Promise.all([
    fetchUser(),
    fetchOrders(),
    fetchMessages()
  ]);
  
  return { user, orders, messages };
}

混合使用模式

     1. 在 async 函数中使用 Promise 链

async function complexOperation() {
  const data = await fetchInitialData();
  
  // 对部分操作使用Promise链
  return processPart1(data)
    .then(processPart2)
    .then(processPart3)
    .catch(handlePartialError);
}

     2. 在 Promise 链中使用 async 函数

function dataPipeline() {
  return getRawData()
    .then(async rawData => {
      const cleaned = await cleanData(rawData);
      return transformData(cleaned);
    })
    .then(finalize)
    .catch(handleError);
}

执行机制对比

     Promise 链执行流程

console.log("开始");

new Promise(resolve => {
  console.log("Promise 执行器");
  setTimeout(() => resolve("结果"), 0);
})
.then(result => console.log("then:", result))
.finally(() => console.log("finally"));

console.log("结束");

/* 输出:
开始
Promise 执行器
结束
then: 结果
finally
*/

     async/await 执行流程

console.log("开始");

async function demo() {
  console.log("函数开始");
  const result = await new Promise(resolve => 
    setTimeout(() => resolve("结果"), 0)
  );
  console.log("await 结果:", result);
  console.log("函数结束");
}

demo();

console.log("结束");

/* 输出:
开始
函数开始
结束
await 结果: 结果
函数结束
*/

错误处理差异

     Promise 链错误传播

function example() {
  return Promise.resolve()
    .then(() => {
      throw new Error("步骤1错误");
    })
    .then(() => {
      console.log("这步不会执行");
    })
    .catch(error => {
      console.error("捕获错误:", error.message); // 捕获错误: 步骤1错误
    });
}

     async/await 错误传播

async function example() {
  try {
    await Promise.resolve();
    throw new Error("步骤1错误");
    console.log("这步不会执行");
  } catch (error) {
    console.error("捕获错误:", error.message); // 捕获错误: 步骤1错误
  }
}

性能与最佳实践

     1. 避免不必要的 await

// 不推荐 (顺序执行)
const user = await getUser();
const posts = await getPosts(); // 等待getUser完成

// 推荐 (并行执行)
const [user, posts] = await Promise.all([getUser(), getPosts()]);

     2. 合理使用 try/catch 范围

// 不推荐 - 过大的try块
try {
  const a = await step1();
  const b = await step2(a);
  const c = await step3(b);
  return c;
} catch (error) {
  // 难以确定错误来源
}

// 推荐 - 精细错误处理
try {
  const a = await step1();
  try {
    const b = await step2(a);
    return await step3(b);
  } catch (error) {
    // 处理step2/step3错误
  }
} catch (error) {
  // 处理step1错误
}

     3. 重要清理使用 finally

let resource;
try {
  resource = await acquireResource();
  await useResource(resource);
} finally {
  if (resource) {
    await releaseResource(resource); // 确保资源释放
  }
}

浏览器兼容性解决方案

     1. 不支持 Promise 的环境

<!-- 引入 polyfill -->
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>

     2. 不支持 async/await 的环境

// 使用 Babel 转译
// .babelrc 配置
{
  "presets": ["@babel/preset-env"]
}

     3. 回退方案

// 检查是否支持 async/await
if (typeof async function() {}.constructor === "function") {
  // 使用现代语法
} else {
  // 使用 Promise 或回调
}

总结:何时选择哪种方式

场景推荐方案原因
简单链式操作Promise 链简洁直接
复杂异步逻辑async/await可读性更好
并行操作Promise.all + await高效简洁
错误恢复async/await + try/catch更精细控制
资源清理finally (两者都适用)确保执行
浏览器兼容Promise + polyfill更广泛支持

现代 JavaScript 最佳实践:

  • 主要逻辑使用 async/await 提高可读性
  • 并行操作使用 Promise.all() 优化性能
  • 始终使用 try/catch.catch() 处理错误
  • 资源清理使用 finally 确保执行
  • 旧环境使用 Babel 转译和 polyfill

通过掌握 Promise 链和 async/await 两种风格及其转换,你可以在不同场景中选择最合适的异步编程方式,编写出更健壮、可维护的 JavaScript 代码。

















JavaScript 异步编程核心:Promiseasync/await 详解

     1. Promise 核心概念 Promise 是异步操作的容器,有三种状态:

  • Pending(进行中)
  • Fulfilled(已成功)
  • Rejected(已失败)
// 创建 Promise
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    Math.random() > 0.5 ? 
      resolve("成功! 🎉") : 
      reject(new Error("失败! ❌"));
  }, 1000);
});

     2. Promise 方法链

// 完整方法链
promise
  .then(result => {
    console.log("Step 1:", result);
    return result + " 处理数据"; // 传递给下一步
  })
  .then(processed => {
    console.log("Step 2:", processed);
    throw new Error("中途出错"); // 模拟错误
  })
  .catch(error => {
    console.error("捕获错误:", error.message);
    return "错误恢复数据"; // 从错误中恢复
  })
  .finally(() => {
    console.log("清理资源 - 总会执行 ♻️");
  })
  .then(finalResult => {
    console.log("最终结果:", finalResult);
  });

/* 可能的输出:
Step 1: 成功! 🎉
Step 2: 成功! 🎉 处理数据
捕获错误: 中途出错
清理资源 - 总会执行 ♻️
最终结果: 错误恢复数据
*/

     3. async/await - Promise 的语法糖

// 基本用法
async function fetchData() {
  try {
    const result = await promise;  // 等待 Promise 解决
    const processed = await process(result);  // 串行操作
    return processed;
  } catch (error) {
    console.error("Async错误:", error.message);
    return "默认数据";
  } finally {
    console.log("Async清理完成");
  }
}

// 使用 async 函数
fetchData().then(console.log);

     4. 关键特性对比

方法作用返回值使用场景
.then()处理成功状态新 Promise链式操作中的数据处理
.catch()捕获拒绝状态/错误新 Promise错误处理
.finally()无论成功失败都执行新 Promise资源清理(关闭文件/连接)
await暂停 async 函数执行等待 Promise 解决Promise 解决值替代 .then() 的同步写法
async声明异步函数自动包装为 Promise包含 await 操作的函数

     5. 最佳实践示例

// 1. 并行请求优化
async function loadDashboard() {
  const [user, orders, notifications] = await Promise.all([
    fetchUser(),
    fetchOrders(),
    fetchNotifications()
  ]);
  return { user, orders, notifications };
}

// 2. 错误处理策略
async function safeOperation() {
  try {
    const data = await riskyFetch();
    return data;
  } catch (err) {
    if (err instanceof NetworkError) {
      return cachedData;  // 网络错误使用缓存
    }
    throw err;  // 其他错误重新抛出
  }
}

// 3. 取消超时请求
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 });
    clearTimeout(timeoutId);
    return response.json();
  } catch (err) {
    if (err.name === 'AbortError') throw new Error(`请求超时 (${timeout}ms)`);
    throw err;
  }
}

// 4. 循环中的并发控制
async function processBatch(items, concurrency = 5) {
  const results = [];
  
  for (let i = 0; i < items.length; i += concurrency) {
    const batch = items.slice(i, i + concurrency);
    const batchResults = await Promise.all(
      batch.map(item => processItem(item))
    );
    results.push(...batchResults);
  }
  
  return results;
}

     6. 常见陷阱与解决方案

  1. 忘记 await

    // ❌ 错误:忘记 await
    async function saveData() {
      const data = fetchData(); // 返回 Promise 而非实际数据
      store.save(data);         // 保存的是 Promise 对象
    }
    
    // ✅ 正确
    async function saveData() {
      const data = await fetchData(); // 等待实际数据
      store.save(data);
    }
    
  2. 循环中的串行执行

    // ❌ 低效:顺序执行
    for (const url of urls) {
      await fetch(url); // 每次等待完成
    }
    
    // ✅ 高效:并行执行
    await Promise.all(urls.map(url => fetch(url)));
    
  3. 忽略错误处理

    // ❌ 危险:未处理可能的拒绝
    async function main() {
      const data = await fetchData();
      render(data);
    }
    
    // ✅ 安全:添加错误处理
    async function main() {
      try {
        const data = await fetchData();
        render(data);
      } catch (err) {
        showErrorUI(err);
      }
    }
    
  4. 资源泄漏

    // ❌ 危险:可能未关闭连接
    async function queryDB() {
      const db = await connect();
      const data = await db.query("SELECT...");
      return data; // 忘记关闭连接!
    }
    
    // ✅ 安全:使用 finally 确保清理
    async function queryDB() {
      const db = await connect();
      try {
        return await db.query("SELECT...");
      } finally {
        await db.close(); // 始终执行
      }
    }
    

     7. 高级模式

// 1. 重试机制
async function retry(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (err) {
    if (retries === 0) throw err;
    await new Promise(r => setTimeout(r, delay));
    return retry(fn, retries - 1, delay * 2); // 指数退避
  }
}

// 2. 异步互斥锁
class AsyncLock {
  constructor() {
    this.queue = Promise.resolve();
  }
  
  acquire() {
    let release;
    const newPromise = new Promise(r => release = r);
    const next = this.queue.then(() => release);
    this.queue = newPromise;
    return next;
  }
}

// 使用锁
const lock = new AsyncLock();
async function safeUpdate() {
  const release = await lock.acquire();
  try {
    await criticalSection();
  } finally {
    release();
  }
}

总结流程图

         创建 Promise
              │
              ▼
        ┌── Pending ──┐
        │             │
    Resolve()     Reject()
        │             │
        ▼             ▼
   then()处理 ◄─── catch()处理
        │             │
        └──► finally() ◄──┘
              │
              ▼
        async/await 封装
              │
        try/catch/finally

掌握这些核心模式和最佳实践,你将能:

  1. 优雅处理复杂异步流程
  2. 编写可维护的异步代码
  3. 避免常见陷阱和性能问题
  4. 实现高级异步控制模式
  5. 提升应用稳定性和用户体验

















promise , then , catch , finally

JavaScript Promise 完全指南:then、catch、finally 详解

一、Promise 核心概念

     什么是 Promise? Promise 是 JavaScript 中处理异步操作的对象,它表示一个尚未完成但预期会完成的操作,解决了传统的回调地狱问题。

     Promise 的三种状态

stateDiagram-v2
    [*] --> Pending
    Pending --> Fulfilled: resolve(value)
    Pending --> Rejected: reject(reason)
    Fulfilled --> [*]
    Rejected --> [*]

     创建 Promise

const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve('操作成功!'); // 状态变为 Fulfilled
    } else {
      reject(new Error('操作失败')); // 状态变为 Rejected
    }
  }, 1000);
});

二、.then() 方法详解

     基本用法

promise.then(
  value => console.log('成功:', value), // Fulfilled 回调
  error => console.error('失败:', error) // Rejected 回调(可选)
);

     链式调用

fetchData()
  .then(response => response.json()) // 第一步
  .then(data => process(data))      // 第二步
  .then(result => save(result))     // 第三步
  .catch(handleError);              // 错误处理

     链式调用规则

回调返回值类型下一个 .then() 接收
普通值作为参数传递给下一个 .then()
Promise 对象等待该 Promise 解决后再传递
抛出错误触发后续 .catch()

三、.catch() 方法详解

     错误处理

fetchData()
  .then(process)
  .catch(error => {
    console.error('捕获错误:', error);
    return fallbackValue; // 返回新值继续链式调用
  });

     错误传播特性

getUser()
  .then(user => getPosts(user.id)) // 此处可能出错
  .then(posts => render(posts))    // 此处可能出错
  .catch(error => {                // 捕获所有错误
    showError(error);
  });

     与 try/catch 的对比

// Promise 方式
fetchData()
  .then(process)
  .catch(handleError);

// async/await 方式
async function fetchAndProcess() {
  try {
    const data = await fetchData();
    return process(data);
  } catch (error) {
    handleError(error);
  }
}

四、.finally() 方法详解

     基本用法

let isLoading = true;

fetchData()
  .then(render)
  .catch(showError)
  .finally(() => {
    isLoading = false; // 无论成功失败都会执行
    cleanResources();
  });

     关键特性

  1. 不接收参数:无法知道 Promise 的最终状态
  2. 不影响结果:传递原始 Promise 的结果
  3. 总是执行:无论成功或失败都会调用

     执行顺序示例

Promise.resolve('原始值')
  .finally(() => console.log('finally 执行'))
  .then(value => console.log('then 接收:', value));

// 输出:
// finally 执行
// then 接收: 原始值

五、完整链式调用示例

     完整工作流

function fetchUserData(userId) {
  let loading = true;
  
  return fetch(`/users/${userId}`)
    .then(response => {
      if (!response.ok) throw new Error('网络错误');
      return response.json();
    })
    .then(user => {
      console.log('用户数据:', user);
      return fetch(`/posts?user=${userId}`);
    })
    .then(response => response.json())
    .catch(error => {
      console.error('请求失败:', error);
      return getCachedData(userId); // 错误恢复
    })
    .finally(() => {
      loading = false;
      console.log('请求完成');
    });
}

// 使用
fetchUserData(123)
  .then(data => displayData(data));

六、静态方法详解

     Promise.all()

// 所有Promise成功时返回结果数组
Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
])
.then(([user, posts, comments]) => {
  renderDashboard(user, posts, comments);
})
.catch(error => {
  console.log('至少一个请求失败', error);
});

     Promise.race()

// 返回第一个完成的Promise(无论成功/失败)
Promise.race([
  fetchData(),
  timeout(5000) // 超时控制
])
.then(data => process(data))
.catch(error => {
  if (error.message === '超时') {
    useCachedData();
  }
});

     其他静态方法

方法描述使用场景
Promise.resolve()创建已解决的PromisePromise.resolve('值')
Promise.reject()创建已拒绝的PromisePromise.reject(new Error())
Promise.allSettled()所有完成后返回结果需要知道每个Promise的最终状态
Promise.any()返回第一个成功的Promise多个备用数据源

七、最佳实践

     1. 错误处理策略

// 推荐方式:链尾统一捕获
fetchData()
  .then(step1)
  .then(step2)
  .catch(handleError); // 捕获所有错误

// 特定步骤错误处理
fetchData()
  .then(step1, handleStep1Error) // 仅处理step1错误
  .then(step2)
  .catch(handleOtherErrors); // 处理其他错误

     2. 避免常见陷阱

// 错误:忘记返回Promise
fetchData()
  .then(response => {
    process(response); // ❌ 忘记return
  });

// 正确:返回结果
fetchData()
  .then(response => {
    return process(response); // ✅
  });

     3. 资源清理模式

function withResource(resource) {
  return fetchData()
    .then(process)
    .finally(() => {
      resource.release(); // 确保资源释放
    });
}

八、实际应用场景

     1. 用户登录流程

function login(username, password) {
  showLoader();
  
  return authenticate(username, password)
    .then(user => fetchProfile(user.id))
    .then(profile => loadPreferences(profile.id))
    .catch(error => {
      showErrorMessage(error);
      return retryLogin();
    })
    .finally(() => {
      hideLoader();
    });
}

     2. 文件上传

function uploadFile(file, onProgress) {
  const formData = new FormData();
  formData.append('file', file);
  
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    
    xhr.upload.onprogress = e => {
      if (e.lengthComputable) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    };
    
    xhr.onload = () => resolve(xhr.response);
    xhr.onerror = () => reject(new Error('上传失败'));
    
    xhr.open('POST', '/upload');
    xhr.send(formData);
  });
}

// 使用
uploadFile(file, updateProgressBar)
  .then(showSuccess)
  .catch(showError);

     3. 数据加载与缓存

function loadDataWithCache(url) {
  // 先尝试从缓存获取
  return getCachedData(url)
    .catch(() => {
      // 缓存失败则从网络加载
      return fetch(url)
        .then(response => response.json())
        .then(data => {
          cacheData(url, data); // 缓存结果
          return data;
        });
    })
    .finally(updateLoadingState);
}

九、与 async/await 结合使用

     在 async 函数中使用 Promise

async function getUserPosts(userId) {
  try {
    const user = await getUser(userId);
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error('加载失败:', error);
    return loadCachedPosts();
  } finally {
    trackAnalytics();
  }
}

     在 Promise 链中使用 async

function processData() {
  return fetchData()
    .then(data => transformAsync(data)) // 调用async函数
    .then(result => save(result));
}

async function transformAsync(data) {
  const cleaned = await cleanData(data);
  return enrichData(cleaned);
}

十、总结

     Promise 方法对比表

方法接收参数返回值决定状态传递使用场景
.then()值/错误回调返回值可改变主要操作流程
.catch()错误对象回调返回值可修复错误处理
.finally()忽略保持原始资源清理

     最佳实践指南

  1. 优先使用 .then():处理主要异步流程
  2. 链尾使用 .catch():统一处理错误
  3. 合理使用 .finally():执行清理操作
  4. 避免嵌套:保持链式调用扁平化
  5. 结合 async/await:复杂逻辑中使用同步风格

     黄金法则

"Promise 是 JavaScript 异步编程的基石,掌握 .then().catch().finally() 的使用,能够让你编写出更健壮、更易维护的异步代码。"

通过深入理解 Promise 及其方法,您将能够:

  • 优雅处理异步操作
  • 避免回调地狱
  • 实现更可靠的错误处理
  • 编写更清晰、更易维护的代码
  • 构建高性能的 JavaScript 应用

















async , await

JavaScript async/await 详解:现代异步编程的终极解决方案

一、async/await 的本质

async/await 是 ES2017 (ES8) 引入的 JavaScript 特性,它们建立在 Promise 之上,提供了更简洁、更直观的方式来处理异步操作:

// async 函数声明
async function fetchData() {
  // await 暂停执行,等待 Promise 解决
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

// 使用 async 函数
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

     核心概念:

  1. async:声明异步函数,隐式返回 Promise
  2. await:暂停函数执行,等待 Promise 解决
  3. 错误处理:使用 try/catch 捕获异步错误

二、async 函数详解

     1. 基本特性

// 1. 总是返回 Promise
async function example() {
  return 42; // 等价于 Promise.resolve(42)
}

// 2. 抛出错误会返回拒绝的 Promise
async function fail() {
  throw new Error('失败'); // 等价于 Promise.reject(new Error('失败'))
}

// 3. 返回 Promise 会直接返回该 Promise
async function getData() {
  return fetch('/api/data'); // 直接返回 fetch 的 Promise
}

     2. 执行流程

console.log('开始执行');

async function asyncFunc() {
  console.log('async 函数开始');
  const result = await new Promise(resolve => 
    setTimeout(() => resolve('完成!'), 1000)
  );
  console.log(result);
  return '函数结束';
}

asyncFunc().then(console.log);
console.log('继续执行');

/* 输出顺序:
开始执行
async 函数开始
继续执行
(1秒后) 完成!
函数结束
*/

三、await 表达式详解

     1. 基本用法

async function process() {
  // 等待 Promise 解决
  const value = await somePromise;
  
  // 等待普通值 (自动包装为 resolved Promise)
  const num = await 42;
  
  // 等待异步函数
  const data = await fetchData();
}

     2. 错误处理

async function loadResource() {
  try {
    const response = await fetch('/api/resource');
    if (!response.ok) {
      throw new Error('网络响应错误');
    }
    return await response.json();
  } catch (error) {
    console.error('加载失败:', error);
    return { defaultValue: true };
  } finally {
    console.log('清理资源');
  }
}

     3. 并行执行优化

// 低效:顺序执行
async function slow() {
  const a = await fetchA(); // 等待完成
  const b = await fetchB(); // 再开始
  return [a, b];
}

// 高效:并行执行
async function fast() {
  // 同时启动所有请求
  const promiseA = fetchA();
  const promiseB = fetchB();
  
  // 等待所有完成
  const [a, b] = await Promise.all([promiseA, promiseB]);
  return [a, b];
}

四、async/await 与 Promise 的关系

     1. 转换规则

Promise 代码async/await 代码
promise.then(onFulfilled)const result = await promise
promise.catch(onRejected)try/catch
promise.finally()finally
链式调用顺序 await 语句

     2. 相互转换示例

// Promise 链 → async/await
function getUserPosts(userId) {
  return getUser(userId)
    .then(user => getPosts(user.id))
    .then(posts => filterPosts(posts));
}

async function getUserPosts(userId) {
  const user = await getUser(userId);
  const posts = await getPosts(user.id);
  return filterPosts(posts);
}

// async/await → Promise
async function calculateTotal() {
  const a = await getValueA();
  const b = await getValueB();
  return a + b;
}

function calculateTotal() {
  return getValueA()
    .then(a => getValueB()
      .then(b => a + b));
}

五、高级用法与模式

     1. 循环中的异步处理

// 顺序执行
async function processSequentially(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// 并行执行
async function processInParallel(items) {
  await Promise.all(items.map(item => processItem(item)));
}

// 控制并发数
async function processWithConcurrency(items, max = 5) {
  const results = [];
  
  for (let i = 0; i < items.length; i += max) {
    const chunk = items.slice(i, i + max);
    const chunkResults = await Promise.all(chunk.map(processItem));
    results.push(...chunkResults);
  }
  
  return results;
}

     2. 异步迭代器 (for-await-of)

async function processStream() {
  const stream = getReadableStream(); // 可读流或异步生成器
  
  for await (const chunk of stream) {
    await processChunk(chunk);
  }
}

     3. 异步生成器

async function* asyncGenerator(start, end) {
  for (let i = start; i <= end; i++) {
    // 模拟异步操作
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

(async () => {
  for await (const num of asyncGenerator(1, 5)) {
    console.log(num); // 1, 2, 3, 4, 5 (每100ms输出一个)
  }
})();

六、错误处理最佳实践

     1. 分层错误处理

async function mainProcess() {
  try {
    // 核心业务逻辑
    const input = await loadInput();
    const processed = await processData(input);
    await saveResults(processed);
  } catch (error) {
    // 业务级错误处理
    if (error instanceof ValidationError) {
      return { status: 'invalid', error };
    }
    
    if (error instanceof NetworkError) {
      await retryOperation();
      return { status: 'retrying' };
    }
    
    // 无法处理的错误重新抛出
    throw error; 
  }
}

// 顶层错误处理
mainProcess()
  .catch(error => {
    console.error('未处理的错误:', error);
    sendErrorReport(error);
  });

     2. 全局错误处理

// 浏览器环境
window.addEventListener('unhandledrejection', event => {
  event.preventDefault();
  console.error('未处理的Promise拒绝:', event.reason);
});

// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的拒绝:', reason);
});

七、性能优化策略

     1. 避免不必要的顺序等待

// 优化前:不必要的顺序执行
async function loadDashboard() {
  const user = await fetchUser();    // 等待完成
  const posts = await fetchPosts();  // 再开始
  const stats = await fetchStats();  // 再开始
  return { user, posts, stats };
}

// 优化后:并行执行
async function loadDashboard() {
  // 同时启动所有请求
  const [user, posts, stats] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchStats()
  ]);
  
  return { user, posts, stats };
}

     2. 缓存 Promise 结果

const userCache = new Map();

async function getUser(userId) {
  if (!userCache.has(userId)) {
    userCache.set(userId, fetchUser(userId));
  }
  return userCache.get(userId);
}

     3. 避免阻塞事件循环

async function processLargeArray(array) {
  const results = [];
  const CHUNK_SIZE = 1000;
  
  for (let i = 0; i < array.length; i += CHUNK_SIZE) {
    const chunk = array.slice(i, i + CHUNK_SIZE);
    
    // 允许事件循环处理其他任务
    await new Promise(resolve => setTimeout(resolve, 0));
    
    results.push(...processChunk(chunk));
  }
  
  return results;
}

八、现代特性

     1. 顶层 await (ES2022)

// 在模块顶层直接使用 await
const config = await loadConfig();
export default config;

     2. Promise.withResolvers (ES2024)

// 创建可外部控制的 Promise
const { promise, resolve, reject } = Promise.withResolvers();

// 在事件处理中解决
document.getElementById('btn').addEventListener('click', () => {
  resolve('按钮被点击');
});

// 使用
const result = await promise;
console.log(result); // "按钮被点击"

九、常见陷阱与解决方案

陷阱错误示例解决方案
忘记 awaitsaveData(data);await saveData(data);
循环中的错误处理items.forEach(async item => { await process(item); })使用 for...of 循环
不必要的顺序化await p1; await p2;使用 Promise.all([p1, p2])
资源未释放缺少 finally 块使用 try/finallytry/catch/finally
Promise 未处理拒绝fetch('/api');添加错误处理逻辑

十、总结:async/await 最佳实践

  1. 优先使用 async/await:使异步代码更易读、更接近同步风格
  2. 合理处理错误:使用 try/catch 或 .catch() 处理潜在错误
  3. 优化并行执行:对独立操作使用 Promise.all
  4. 避免阻塞主线程:拆分 CPU 密集型任务
  5. 使用现代特性:利用顶层 await 和 Promise 组合方法
  6. 资源清理:始终在 finally 块中释放资源
  7. 全局错误处理:添加 unhandledrejection 事件监听器
// 最佳实践示例
async function robustAsyncOperation(params) {
  try {
    // 并行执行独立操作
    const [resource, config] = await Promise.all([
      acquireResource(),
      loadConfig()
    ]);
    
    // 顺序执行依赖操作
    const data = await processData(resource, config);
    
    // 返回结果
    return { status: 'success', data };
  } catch (error) {
    // 错误分类处理
    if (error instanceof NetworkError) {
      return { status: 'retry', data: cachedData };
    }
    
    // 未知错误
    console.error('操作失败:', error);
    throw error;
  } finally {
    // 资源清理
    releaseResources();
  }
}

async/await 是现代 JavaScript 异步编程的终极解决方案,它:

  • 使异步代码更易读、更易维护
  • 提供同步风格的错误处理机制
  • 兼容所有现代浏览器和 Node.js
  • 与现有 Promise 代码无缝集成
  • 支持复杂的控制流(循环、条件等)

掌握 async/await 将显著提升你的 JavaScript 异步编程能力,让你能够编写更简洁、更健壮的异步代码。

















Promise,then 与 async,await 相互转换

Promise.then 与 async/await 相互转换指南

     1. 基本转换原理

// Promise.then 风格
function getData() {
  return fetchData()
    .then(data => process(data))
    .catch(err => handleError(err));
}

// async/await 风格
async function getData() {
  try {
    const data = await fetchData();
    return process(data);
  } catch (err) {
    handleError(err);
  }
}

     2. 链式调用转换

// Promise.then 链
fetchUser(userId)
  .then(user => fetchOrders(user.id))
  .then(orders => getLatestOrder(orders))
  .then(order => console.log(order))
  .catch(console.error);

// async/await 等效
async function displayLatestOrder() {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    const order = await getLatestOrder(orders);
    console.log(order);
  } catch (err) {
    console.error(err);
  }
}

     3. 错误处理转换

// .catch 转换
promise
  .then(handleSuccess)
  .catch(handleError);  // 捕获前面所有错误

// async/await 等效
try {
  await promise;
  handleSuccess();
} catch (err) {
  handleError(err);
}

// .finally 转换
promise
  .then(handleResult)
  .finally(cleanup);

// async/await 等效
try {
  const result = await promise;
  handleResult(result);
} finally {
  cleanup();
}

     4. 并行执行转换

// Promise.all 转换
Promise.all([task1(), task2(), task3()])
  .then(([r1, r2, r3]) => combineResults(r1, r2, r3));

// async/await 等效
async function parallelTasks() {
  const [r1, r2, r3] = await Promise.all([task1(), task2(), task3()]);
  return combineResults(r1, r2, r3);
}

// Promise.race 转换
Promise.race([fetchData(), timeout(5000)])
  .then(firstResult => handleResult(firstResult));

// async/await 等效
async function withTimeout() {
  const result = await Promise.race([fetchData(), timeout(5000)]);
  handleResult(result);
}

     5. 循环中的转换

// Promise 链中的循环
function processItems(items) {
  let chain = Promise.resolve();
  
  items.forEach(item => {
    chain = chain.then(() => processItem(item));
  });
  
  return chain;
}

// async/await 等效
async function processItems(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// 并行循环
Promise.all(items.map(item => processItem(item)));

// async/await 等效
async function processAll(items) {
  await Promise.all(items.map(item => processItem(item)));
}

     6. 混合使用技巧

// 在 async 函数中使用 .then
async function fetchWithLogging() {
  const data = await fetchData()
    .then(res => {
      console.log('数据已接收');
      return res;
    })
    .catch(err => {
      console.error('获取失败', err);
      throw err;
    });
  
  return process(data);
}

// 在 .then 中使用 async 函数
fetchUser(userId)
  .then(async user => {
    const orders = await fetchOrders(user.id);
    return { user, orders };
  })
  .then(console.log);

     7. 高级模式转换

// 重试机制转换
// Promise 风格
function retry(fn, retries) {
  return fn().catch(err => 
    retries > 0 
      ? retry(fn, retries - 1) 
      : Promise.reject(err)
  );
}

// async/await 风格
async function retry(fn, retries) {
  try {
    return await fn();
  } catch (err) {
    if (retries <= 0) throw err;
    return retry(fn, retries - 1);
  }
}

// 取消机制
// Promise 风格
function cancellable(promise) {
  let cancel;
  const wrapped = new Promise((resolve, reject) => {
    cancel = reject;
    promise.then(resolve).catch(reject);
  });
  return [wrapped, () => cancel(new Error('操作取消'))];
}

// async/await 风格
async function cancellable(asyncFn) {
  const controller = new AbortController();
  const promise = asyncFn(controller.signal);
  
  return {
    result: promise,
    cancel: () => controller.abort()
  };
}

转换规则速查表

Promise.then 模式async/await 等效
.then(response => {...})const response = await ...
.catch(error => {...})try/catch
.finally(() => {...})try/catch 后的 finally
Promise.all([p1, p2])const [r1, r2] = await Promise.all([p1, p2])
Promise.race([p1, p2])const result = await Promise.race([p1, p2])
new Promise((resolve, reject) => {...})直接使用 async 函数
.then().then().catch()多个 await + try/catch
返回 Promise.resolve(value)直接 return value
返回 Promise.reject(error)throw error

最佳实践建议

  1. 优先使用 async/await

    // 更推荐
    async function getUserData() {
      const user = await fetchUser();
      return transform(user);
    }
    
  2. 在需要精细控制时使用 .then

    // 特定场景更合适
    function logFetch() {
      fetchData()
        .then(data => console.log('收到数据:', data))
        .catch(err => console.error('错误:', err));
    }
    
  3. 混合使用时保持清晰

    // 良好的混合示例
    async function processPipeline() {
      const rawData = await fetchData();
      
      return transform(rawData)
        .then(result => {
          logResult(result);
          return result;
        })
        .catch(err => {
          handleTransformError(err);
          throw err;
        });
    }
    
  4. 避免常见陷阱

    // 错误:忘记 await
    async function example() {
      const data = fetchData(); // 应该是 await fetchData()
      console.log(data); // 输出 Promise 对象
    }
    
    // 错误:在循环中误用 await
    async function slowLoop() {
      for (const url of urls) {
        await fetch(url); // 顺序执行,效率低下
      }
    }
    
    // 正确:并行执行
    async function fastLoop() {
      await Promise.all(urls.map(url => fetch(url)));
    }
    

掌握这些转换技巧可以让你:

  1. 在旧代码库中逐步引入 async/await
  2. 根据场景选择最合适的异步风格
  3. 阅读和理解不同风格的异步代码
  4. 编写更清晰、更易维护的异步逻辑

















then的第二个回调 与 catch回调

JavaScript Promise 错误处理详解:.then 的第二个回调 vs .catch

在 Promise 链式调用中,错误处理有两种主要方式:.then() 的第二个回调函数和 .catch() 方法。虽然它们都用于处理 rejected 状态,但在行为上有重要区别。

核心区别对比

特性.then 的第二个回调.catch 回调
作用范围仅处理前一个 Promise 的 rejection捕获链中前面所有未处理的 rejection
错误传递处理当前错误后,后续链继续正常执行处理错误后,后续链继续正常执行
位置要求必须紧跟在前一个 Promise 后可放在链的任何位置
链式影响不影响后续错误处理会拦截所有前面的未处理错误
推荐场景特定步骤的错误恢复全局错误处理
返回值影响返回的值传递给下一个 .then()返回的值传递给下一个 .then()

详细解析与示例

     1. 作用范围差异

.then 的第二个回调只处理它直接连接的 Promise 的 rejection:

// 只处理 promiseA 的拒绝
promiseA
  .then(
    result => console.log("成功:", result),
    error => console.log("处理错误A:", error) // 只处理 promiseA 的拒绝
  )
  .then(() => promiseB) // 如果 promiseB 被拒绝
  .catch(error => console.log("捕获错误B:", error)); // 会捕获 promiseB 的拒绝

.catch 回调会捕获链中前面所有未处理的 rejection:

promiseA
  .then(result => console.log("步骤1:", result))
  .then(() => promiseB) // 如果 promiseB 被拒绝
  .catch(error => console.log("捕获所有错误:", error)) // 捕获 promiseA 或 promiseB 的拒绝
  .then(() => promiseC) // 如果 promiseC 被拒绝
  .catch(error => console.log("捕获新错误:", error)); // 捕获 promiseC 的拒绝

     2. 错误恢复流程

两种方式都能从错误中恢复,但作用域不同:

// 使用 .then 的第二个回调局部恢复
fetchUserData()
  .then(
    user => updateProfile(user.id),
    error => {
      console.log("用户数据获取失败,使用默认值");
      return getDefaultUser(); // 返回恢复值
    }
  )
  .then(user => console.log("处理用户:", user));

// 使用 .catch 全局恢复
processOrder()
  .then(order => chargePayment(order))
  .catch(error => {
    console.log("支付失败:", error);
    return retryPayment(); // 全局恢复
  })
  .then(() => sendConfirmation());

     3. 组合使用模式

实际开发中经常组合使用两者:

fetchInitialData()
  .then(validateData, handleValidationError) // 特定步骤错误处理
  .then(processData)
  .then(saveResults)
  .catch(handleGlobalError); // 全局错误兜底

     4. 错误冒泡行为

未被处理的 rejection 会一直向下传递:

Promise.reject(new Error("原始错误"))
  .then(result => console.log("步骤1")) // 跳过
  .then(
    result => console.log("步骤2"),
    error => console.log("处理原始错误:", error) // 捕获原始错误
  )
  .then(() => Promise.reject(new Error("新错误"))) // 新错误
  .catch(error => console.log("处理新错误:", error)); // 捕获新错误

最佳实践建议

     1. 优先使用 .catch 的场景

  • 全局错误处理:在链的末尾添加兜底处理
  • 简化代码:当不需要区分特定步骤错误时
  • 错误日志:集中记录所有未处理的错误
// 推荐:简洁的全局错误处理
apiCall()
  .then(processData)
  .then(saveResult)
  .catch(error => {
    console.error("操作失败:", error);
    notifyAdmin(error);
  });

     2. 使用 .then 第二个参数的场景

  • 特定步骤恢复:某个操作有特殊的恢复逻辑
  • 错误转换:将特定错误转换为更有意义的错误类型
  • 条件处理:只处理特定类型的错误
// 特定步骤的错误恢复
fetchExternalAPI()
  .then(
    data => parseData(data),
    error => {
      if (error.status === 404) {
        return fetchFallbackData(); // 特定恢复
      }
      throw error; // 其他错误继续传递
    }
  )
  .catch(handleOtherErrors);

     3. 高级错误处理模式

         错误分类处理:

async function processTransaction() {
  try {
    const user = await getUser();
    const payment = await chargePayment(user);
    return await confirmTransaction(payment);
  } catch (error) {
    if (error instanceof NetworkError) {
      return retryAfterDelay();
    } else if (error instanceof PaymentError) {
      return notifyPaymentFailure();
    } else {
      throw error;
    }
  }
}

         错误边界模式:

function withErrorBoundary(promise, fallback) {
  return promise
    .catch(error => {
      logError(error);
      return fallback;
    });
}

// 使用
withErrorBoundary(
  fetchCriticalData(),
  getCachedData()
).then(data => renderUI(data));

常见陷阱与解决方案

     1. 错误处理遗漏

// 危险:可能遗漏错误
promise
  .then(handler1)
  .then(handler2); // 如果 handler2 抛出错误,没有处理

// 解决方案:添加全局 catch
promise
  .then(handler1)
  .then(handler2)
  .catch(globalErrorHandler);

     2. 过度嵌套

// 反模式:过度使用 .then 的第二个参数
promise
  .then(success1, error1)
  .then(success2, error2)
  .then(success3, error3);

// 更好:使用 .catch 简化
promise
  .then(success1)
  .then(success2)
  .then(success3)
  .catch(error => {
    // 统一处理所有错误
  });

     3. 错误吞噬

// 危险:可能吞噬错误
promise
  .then(result => {
    throw new Error("处理错误");
  }, handleError) // 这个错误处理不会捕获上面抛出的错误
  .catch(globalErrorHandler); // 这里才会捕获

// 明确:每个错误处理只负责特定范围
promise
  .then(
    result => process(result), 
    error => handleInitialError(error)
  )
  .then(nextStep)
  .catch(handleAllErrors);

决策流程图

graph TD
    A[需要处理错误] --> B{需要特定步骤处理?}
    B -->|是| C[使用 .then 的第二个参数]
    B -->|否| D[使用 .catch]
    C --> E{需要全局兜底?}
    D --> E
    E -->|是| F[在链尾添加 .catch]
    E -->|否| G[结束]

总结与黄金法则

  1. 优先使用 .catch:作为全局错误处理器,放在链的末尾
  2. 特定恢复使用 .then 第二个参数:当某个步骤需要特殊错误处理
  3. 明确每个处理器的职责范围
    • .then 的第二个参数:只处理前一个 Promise 的拒绝
    • .catch:处理前面所有未处理的拒绝
  4. 始终添加全局错误处理:防止未处理的 Promise 拒绝
  5. 在错误处理器中明确控制流程
    • 返回正常值:继续执行后续操作
    • 抛出错误:将错误传递给下一个处理器
// 黄金实践示例
criticalOperation()
  .then(
    result => specificProcess(result), 
    error => handleSpecificError(error) // 特定错误恢复
  )
  .then(nextStep)
  .then(finalStep)
  .catch(error => { // 全局兜底
    console.error("操作失败:", error);
    emergencyRecovery();
  })
  .finally(cleanupResources);

理解 .then 的第二个回调与 .catch 的区别,能让你更精准地控制 Promise 链中的错误处理流程,构建更健壮的异步 JavaScript 应用。







































JavaScript异步编程: 回调callback , Promise,then,catch,finally , async,await 笔记250810







JavaScript异步,笔记250810







JavaScript的 Promise,then,catch,finally,async,await 笔记250810







JavaScript的 Promise,then 笔记250803







Js的 Promise的 then catch 笔记240222







Promise().then().catch().finally() 笔记250804







Promise的reject处理: then的第二个回调 与 catch回调 笔记250804







JavaScript的 async , await 笔记250808







Promise,then 与 async,await 相互转换 笔记250810







JavaScript的Callback回调函数转Promise().then()和async{await} 笔记250810