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

35 阅读30分钟

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

JavaScript 回调函数转 Promise 和 async/await 指南

在 JavaScript 中,回调函数是处理异步操作的传统方式,但随着语言的发展,Promise 和 async/await 提供了更优雅的解决方案。下面我将展示如何将回调函数转换为 Promise 和 async/await 模式。

回调函数的基本模式

典型的回调函数模式(Node.js 错误优先风格):

function asyncOperation(arg, callback) {
  // 模拟异步操作
  setTimeout(() => {
    if (arg === 'error') {
      callback(new Error('发生错误'));
    } else {
      callback(null, `结果: ${arg}`);
    }
  }, 100);
}

// 使用回调
asyncOperation('数据', (err, result) => {
  if (err) {
    console.error('错误:', err.message);
    return;
  }
  console.log('回调结果:', result);
});

转换为 Promise

     1. 手动包装为 Promise

function promiseOperation(arg) {
  return new Promise((resolve, reject) => {
    asyncOperation(arg, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

// 使用 Promise
promiseOperation('数据')
  .then(result => console.log('Promise 结果:', result))
  .catch(err => console.error('Promise 错误:', err.message));

     2. 使用 util.promisify(Node.js 专用)

const util = require('util');

// 自动转换
const promisifiedOperation = util.promisify(asyncOperation);

// 使用
promisifiedOperation('数据')
  .then(result => console.log('Promisify 结果:', result))
  .catch(err => console.error('Promisify 错误:', err.message));

转换为 async/await

     1. 基于手动 Promise 包装

async function asyncAwaitOperation(arg) {
  try {
    const result = await promiseOperation(arg);
    console.log('async/await 结果:', result);
    return result;
  } catch (err) {
    console.error('async/await 错误:', err.message);
    throw err;
  }
}

// 使用
asyncAwaitOperation('数据');

     2. 直接使用 util.promisify 的结果

async function directAsyncAwait(arg) {
  try {
    const result = await promisifiedOperation(arg);
    console.log('直接 async/await 结果:', result);
    return result;
  } catch (err) {
    console.error('直接 async/await 错误:', err.message);
  }
}

// 使用
directAsyncAwait('数据');

复杂回调结构的转换

     1. 多个参数的回调

// 原始回调函数
function multiArgAsync(arg, callback) {
  setTimeout(() => {
    callback(null, `结果1: ${arg}`, `结果2: ${arg.toUpperCase()}`);
  }, 100);
}

// 转换为 Promise
function promiseMultiArg(arg) {
  return new Promise((resolve, reject) => {
    multiArgAsync(arg, (err, ...results) => {
      if (err) reject(err);
      else resolve(results);
    });
  });
}

// 使用 async/await
async function useMultiArg() {
  const [res1, res2] = await promiseMultiArg('数据');
  console.log('多结果:', res1, res2);
}

     2. 事件监听器转换

// 原始事件监听模式
class EventEmitter {
  constructor() {
    this.listeners = [];
  }
  
  onEvent(callback) {
    this.listeners.push(callback);
  }
  
  trigger(data) {
    this.listeners.forEach(cb => cb(data));
  }
}

// 转换为 Promise
function eventToPromise(emitter) {
  return new Promise((resolve) => {
    emitter.onEvent(resolve);
  });
}

// 使用
const emitter = new EventEmitter();

async function handleEvent() {
  const result = await eventToPromise(emitter);
  console.log('事件结果:', result);
}

// 触发事件
setTimeout(() => emitter.trigger('事件数据'), 500);

转换模式对比

模式优点缺点适用场景
回调函数简单直接,无需额外语法容易产生回调地狱,错误处理复杂简单脚本,传统代码库
Promise链式调用,更好的错误处理需要理解 Promise 链机制现代浏览器,Node.js v8+
async/await类似同步代码,最易读需要 async 函数上下文复杂异步逻辑,现代开发

最佳实践与常见问题

     1. 避免 Promise 构造函数滥用

// 反模式:不必要的 Promise 包装
async function fetchData() {
  return new Promise(resolve => {
    fetch('/api/data')
      .then(response => response.json())
      .then(resolve);
  });
}

// 正确:直接返回 Promise
function fetchData() {
  return fetch('/api/data').then(response => response.json());
}

     2. 正确处理错误

// 错误:遗漏异步错误处理
async function dangerous() {
  const data = await fetchData();
  // 如果 fetchData 抛出错误,此处会终止但无处理
}

// 正确:添加 try/catch
async function safe() {
  try {
    const data = await fetchData();
    return process(data);
  } catch (err) {
    console.error('安全捕获:', err);
    return fallbackData;
  }
}

     3. 混合使用模式

async function optimalApproach() {
  // 并行请求
  const [user, product] = await Promise.all([
    fetchUser(),
    fetchProduct()
  ]);
  
  // 后台操作(无需等待)
  sendAnalytics(user).catch(err => 
    console.warn('分析发送失败:', err)
  );
  
  // 顺序处理
  const order = await createOrder(user, product);
  
  return { user, product, order };
}

转换工具函数

     通用回调转 Promise 函数

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, ...results) => {
        if (err) return reject(err);
        // 处理单结果和多结果情况
        resolve(results.length > 1 ? results : results[0]);
      });
    });
  };
}

// 使用
const asyncOperationPromise = promisify(asyncOperation);

asyncOperationPromise('数据')
  .then(result => console.log('通用转换结果:', result));

实际应用示例

     文件操作转换(Node.js)

const fs = require('fs');

// 回调版本
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) return console.error(err);
  console.log('回调文件内容:', data);
});

// Promise 版本
const readFilePromise = util.promisify(fs.readFile);

readFilePromise('file.txt', 'utf8')
  .then(data => console.log('Promise 文件内容:', data))
  .catch(err => console.error('Promise 文件错误:', err));

// async/await 版本
async function readAndProcess() {
  try {
    const content = await readFilePromise('file.txt', 'utf8');
    const processed = content.toUpperCase();
    await fs.promises.writeFile('output.txt', processed);
    console.log('文件处理完成');
  } catch (err) {
    console.error('文件处理失败:', err);
  }
}

     AJAX 请求转换(浏览器)

// 回调版本
function ajaxCallback(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 ajaxPromise(url) {
  return new Promise((resolve, reject) => {
    ajaxCallback(url, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// 使用 async/await
async function fetchData() {
  try {
    const data = await ajaxPromise('https://api.example.com/data');
    const json = JSON.parse(data);
    console.log('获取数据:', json);
    return json;
  } catch (err) {
    console.error('API 请求失败:', err);
  }
}

总结

将回调函数转换为 Promise 和 async/await 的步骤:

  1. 识别回调模式:通常是错误优先回调
  2. 创建 Promise 包装器
    function promiseVersion(...args) {
      return new Promise((resolve, reject) => {
        originalFunction(...args, (err, result) => {
          if (err) reject(err);
          else resolve(result);
        });
      });
    }
    
  3. 使用 async/await
    async function asyncVersion() {
      try {
        const result = await promiseVersion();
        // 处理结果
      } catch (err) {
        // 处理错误
      }
    }
    

转换带来的好处:

  • 更清晰的代码结构
  • 避免回调地狱
  • 更强大的错误处理能力
  • 更好的异步流程控制

在现代 JavaScript 开发中,建议:

  1. 在新项目中使用 async/await 作为主要异步模式
  2. 使用 util.promisify 或自定义包装器转换遗留回调代码
  3. 对于复杂异步逻辑,结合 Promise 的静态方法(all、race 等)
  4. 始终添加适当的错误处理

















1️⃣ 参考 1

JavaScript 回调函数转 Promise 与 async/await 完全指南

将回调函数转换为 Promise 是现代化 JavaScript 代码的关键步骤,让你能使用 .then() 链和 async/await 语法。以下是详细实现方法:

基础转换方法

     1. 手动创建 Promise 包装器

// 原始回调函数 (Node.js 风格)
function callbackFunction(input, callback) {
  setTimeout(() => {
    const error = Math.random() > 0.8 ? new Error('随机错误') : null;
    const result = `处理结果: ${input}`;
    callback(error, result);
  }, 100);
}

// 转换为 Promise
function promisifiedFunction(input) {
  return new Promise((resolve, reject) => {
    callbackFunction(input, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

// 使用 .then()
promisifiedFunction("数据")
  .then(result => console.log("成功:", result))
  .catch(err => console.error("失败:", err.message));

// 使用 async/await
async function processData() {
  try {
    const result = await promisifiedFunction("数据");
    console.log("结果:", result);
  } catch (err) {
    console.error("错误:", err.message);
  }
}

     2. 通用 promisify 函数

// 通用转换函数
function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      // 添加自定义回调
      const customCallback = (err, ...results) => {
        if (err) return reject(err);
        // 处理单个或多个结果
        resolve(results.length > 1 ? results : results[0]);
      };
      
      // 调用原始函数
      fn.call(this, ...args, customCallback);
    });
  };
}

// 使用示例
const asyncOperation = promisify(callbackFunction);

// .then() 使用
asyncOperation("输入")
  .then(console.log)
  .catch(console.error);

// async/await 使用
async function main() {
  const data = await asyncOperation("输入");
  console.log("接收数据:", data);
}

Node.js 环境专用方法

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

const util = require('util');

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

// 转换整个对象的方法
const fs = require('fs');
const promisifiedFs = {
  readFile: util.promisify(fs.readFile),
  writeFile: util.promisify(fs.writeFile)
};

// 使用 async/await
async function fileOperations() {
  try {
    const content = await promisifiedFs.readFile('file.txt', 'utf8');
    const processed = content.toUpperCase();
    await promisifiedFs.writeFile('output.txt', processed);
    console.log('文件处理完成');
  } catch (err) {
    console.error('文件操作失败:', err);
  }
}

处理特殊回调模式

     1. 多参数回调

// 返回多个值的回调
function multiArgFunction(callback) {
  setTimeout(() => callback(null, '结果1', '结果2'), 100);
}

// 转换方法
const promisifiedMulti = promisify(multiArgFunction);

// 使用
promisifiedMulti()
  .then(([res1, res2]) => console.log(res1, res2));

// async/await 解构
async function getMultiple() {
  const [first, second] = await promisifiedMulti();
  console.log(first, second);
}

     2. 无错误参数的回调 (jQuery 风格)

function jqueryStyle(callback) {
  setTimeout(() => callback('成功结果'), 100);
}

// 转换函数
function promisifyNoError(fn) {
  return function(...args) {
    return new Promise(resolve => {
      fn(...args, resolve);
    });
  };
}

// 使用
const asyncJquery = promisifyNoError(jqueryStyle);
asyncJquery().then(console.log); // 输出: "成功结果"

     3. 事件发射器转换

function eventToPromise(emitter, eventName) {
  return new Promise((resolve, reject) => {
    const cleanup = () => {
      emitter.off(eventName, successHandler);
      emitter.off('error', errorHandler);
    };
    
    const successHandler = (...args) => {
      cleanup();
      resolve(args);
    };
    
    const errorHandler = (err) => {
      cleanup();
      reject(err);
    };
    
    emitter.on(eventName, successHandler);
    emitter.on('error', errorHandler);
  });
}

// 使用示例 - 等待数据库连接
const { EventEmitter } = require('events');
const db = new EventEmitter();

async function connectDatabase() {
  // 模拟连接
  setTimeout(() => db.emit('connected'), 500);
  
  try {
    await eventToPromise(db, 'connected');
    console.log('数据库已连接');
  } catch (err) {
    console.error('连接失败', err);
  }
}

实际应用示例

     1. 文件操作转换

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

// 使用 async/await
async function processFile() {
  try {
    const content = await readFileAsync('document.txt');
    const result = content.replace(/old/g, 'new');
    console.log('处理结果:', result);
  } catch (err) {
    console.error('文件处理失败:', err.message);
  }
}

     2. 数据库操作转换

// 旧式数据库接口
const legacyDB = {
  query: (sql, callback) => {
    setTimeout(() => {
      if (sql.includes('DROP')) {
        callback(new Error('危险操作'));
      } else {
        callback(null, [{ id: 1, name: '测试数据' }]);
      }
    }, 200);
  }
};

// 添加 Promise 接口
legacyDB.queryAsync = (sql) => new Promise((resolve, reject) => {
  legacyDB.query(sql, (err, results) => {
    if (err) reject(err);
    else resolve(results);
  });
});

// 使用 .then() 链
legacyDB.queryAsync('SELECT * FROM users')
  .then(users => {
    console.log('用户数量:', users.length);
    return legacyDB.queryAsync('SELECT * FROM orders');
  })
  .then(orders => console.log('订单数量:', orders.length))
  .catch(err => console.error('查询失败:', err.message));

最佳实践与技巧

     1. 保持向后兼容

function dualInterface(input, callback) {
  // 如果有回调函数,使用回调模式
  if (typeof callback === 'function') {
    callbackFunction(input, (err, result) => {
      callback(err, result);
    });
    return;
  }
  
  // 否则返回 Promise
  return new Promise((resolve, reject) => {
    callbackFunction(input, (err, result) => {
      err ? reject(err) : resolve(result);
    });
  });
}

// 两种使用方式
// 回调方式
dualInterface("输入", (err, result) => {
  if (err) console.error(err);
  else console.log(result);
});

// Promise 方式
dualInterface("输入")
  .then(console.log)
  .catch(console.error);

     2. 高级 promisify 函数

function advancedPromisify(fn, context) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      // 创建自定义回调
      const callback = (err, ...results) => {
        if (err) return reject(err);
        resolve(results.length > 1 ? results : results[0]);
      };
      
      try {
        // 调用原始函数
        fn.apply(context || this, [...args, callback]);
      } catch (syncErr) {
        // 捕获同步错误
        reject(syncErr);
      }
    });
  };
}

     3. 错误处理策略

// 添加重试机制的包装器
function withRetry(fn, retries = 3, delay = 1000) {
  return async function(...args) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await fn(...args);
      } catch (err) {
        if (attempt === retries) throw err;
        console.log(`尝试 ${attempt} 失败,${delay}ms 后重试...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= 2; // 指数退避
      }
    }
  };
}

// 使用
const reliableOperation = withRetry(promisifiedFunction, 4);
reliableOperation("重要数据")
  .then(console.log)
  .catch(err => console.error("最终失败:", err));

转换流程图解

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

通过以上方法,你可以:

  1. 将任何回调函数转换为 Promise
  2. 使用 .then().catch() 处理异步操作
  3. 使用 async/await 编写更简洁的代码
  4. 保持与现代 JavaScript 生态兼容
  5. 逐步重构旧代码而不破坏现有功能

关键优势:

  • 消除回调地狱(Callback Hell)
  • 使用 try/catch 统一错误处理
  • 利用 Promise.all 等高级组合功能
  • 改善代码可读性和可维护性
  • 与现代 async/await 语法无缝集成

















2️⃣ 参考 2

以下是 JavaScript 中 回调函数(Callback) 转换为 Promise().then()async/await 的详细指南,包含核心逻辑、示例及注意事项:


 一、Callback → Promise 转换       核心思想 将传统回调风格的异步操作封装为返回 Promise 的函数,利用 resolve/reject 控制状态变更。

     ️ 典型场景 & 改造步骤          场景 1:普通回调函数 原始代码(回调风格):

function fetchData(callback) {
  setTimeout(() => {
    callback("数据"); // 成功时调用回调
    // 如果失败可改为 callback(null, new Error('出错'))
  }, 1000);
}

// 使用方式
fetchData((data, error) => {
  if (error) return console.error(error);
  console.log(data); // 输出: 数据
});

改造为 Promise:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("数据"); // 成功时调用 resolve
      // 失败时调用 reject(new Error('出错'))
    }, 1000);
  });
}

// 使用方式
fetchData()
  .then(data => console.log(data)) // 输出: 数据
  .catch(error => console.error(error));

         场景 2:带多个回调参数的函数 原始代码(回调风格):

function getUserInfo(id, successCb, errorCb) {
  // 模拟异步请求
  setTimeout(() => {
    if (id === 1) {
      successCb({ id: 1, name: "张三" }); // 成功回调
    } else {
      errorCb(new Error("用户不存在")); // 失败回调
    }
  }, 800);
}

// 使用方式
getUserInfo(1, data => console.log(data), err => console.error(err));

改造为 Promise:

function getUserInfo(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 1) {
        resolve({ id: 1, name: "张三" }); // 成功时 resolve
      } else {
        reject(new Error("用户不存在")); // 失败时 reject
      }
    }, 800);
  });
}

// 使用方式
getUserInfo(1)
  .then(data => console.log(data)) // 输出: { id: 1, name: '张三' }
  .catch(err => console.error(err)); // 若 id≠1 则捕获错误

     ⚠️ 关键注意事项

  1. 单一 Promise 对应一次操作:每个异步操作应返回独立 Promise。
  2. 错误统一用 reject():避免混合使用回调传参和 Promise 的错误机制。
  3. 避免 “Pyramid of Doom”:链式调用优于嵌套回调,但仍需注意复杂度。

 二、Callback → async/await 转换      易 核心思想 通过 async 函数 + await 关键字,以同步编码风格书写异步逻辑,底层仍依赖 Promise。

     ️ 典型场景 & 改造步骤          场景 1:基础回调转 async/await 原始代码(回调风格):

function readFile(filename, callback) {
  // 模拟文件读取
  setTimeout(() => {
    callback(null, "文件内容"); // 第一个参数始终为 null(无错误)
  }, 500);
}

// 使用方式
readFile("test.txt", (error, content) => {
  if (error) return console.error(error);
  console.log(content); // 输出: 文件内容
});

改造为 async/await:

function readFile(filename) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("文件内容"); // 实际项目中此处应为真实文件读取逻辑
    }, 500);
  });
}

async function main() {
  try {
    const content = await readFile("test.txt"); // 等待 Promise 解析
    console.log(content); // 输出: 文件内容
  } catch (error) {
    console.error(error); // 捕获所有错误
  }
}
main();

         场景 2:连续依赖的异步操作 原始代码(回调地狱):

function chainOperations(callback) {
  stepOne((result1) => {
    stepTwo(result1, (result2) => {
      stepThree(result2, (finalResult) => {
        callback(finalResult);
      });
    });
  });
}

改造为 async/await:

async function chainOperations() {
  try {
    const result1 = await stepOne(); // 等待第一步完成
    const result2 = await stepTwo(result1); // 等待第二步完成
    const finalResult = await stepThree(result2); // 等待第三步完成
    return finalResult;
  } catch (error) {
    console.error("链路中断:", error);
    throw error; // 根据需求决定是否抛出
  }
}

     ⚠️ 关键注意事项

  1. 必须在 async 函数内使用 await:直接在全局作用域使用 await 会报语法错误。
  2. 错误处理必备 try/catchawait 后的函数若抛出异常,会被 catch 捕获。
  3. 性能考量:过度拆分小任务可能导致微任务队列拥挤,合理合并批量操作(如 Promise.all)。

 三、三种风格的对比分析

特性回调函数Promiseasync/await
代码结构嵌套层级深链式调用同步风格
错误处理需手动传递错误参数.catch()try/catch
调试友好度困难较好极佳(错误堆栈清晰)
学习曲线简单中等易上手(基于同步思维)
适用场景简单脚本、事件监听中等复杂度异步流程复杂业务逻辑、线性流程

✅ 四、最佳实践建议

  1. 优先选择 async/await:代码可读性高,适合大多数场景。
  2. 混合使用时注意顺序
    • 先定义返回 Promise 的基础函数(如 API 请求)。
    • 再用 async/await 组织业务逻辑。
  3. 警惕隐式 Promise 转换:某些库(如 fs 模块)已提供原生 Promise 版本,无需手动封装。
  4. 错误处理全覆盖:无论哪种风格,都要确保错误能被捕获。

 五、进阶技巧       1. 回调函数兼容 Promise 化工具 使用第三方库(如 util.promisify)快速转换 Node.js 回调风格函数:

const { promisify } = require('util');
const readFileAsync = promisify(fs.readFile); // 将回调函数转为 Promise

// 使用方式
readFileAsync("test.txt")
  .then(content => console.log(content))
  .catch(err => console.error(err));

     ⏱️ 2. async/await 中的并行处理

async function parallelTasks() {
  const [user, posts] = await Promise.all([
    fetchUser(),    // 同时发起两个请求
    fetchPosts()
  ]);
  console.log(`用户 ${user.name} 的文章数: ${posts.length}`);
}

      3. 超时控制方案

function requestWithTimeout(url, ms) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), ms);
  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timeoutId)); // 清理定时器
}

// 使用方式
requestWithTimeout("/api/data", 5000)
  .then(response => response.json())
  .catch(err => console.error("请求超时或失败:", err));

 总结

目标实现方式示例代码
回调转 Promise手动封装返回 Promisefunction func() { return new Promise(...) }
回调转 async/await先转 Promise,再用 async/awaitasync function() { await promise; }
复杂异步流程优化结合 Promise.all + async/awaitconst [a, b] = await Promise.all([p1, p2]);

通过以上方法,可以系统性地将回调风格的老代码升级为现代 Promise 或 async/await 写法,提升代码可维护性和可读性。

















3️⃣ 参考 3

JavaScript 回调转 Promise 与 async/await 完全指南

在 JavaScript 异步编程中,将传统的回调函数转换为 Promise 和 async/await 是提升代码可读性和可维护性的关键技能。本文将深入探讨转换方法和最佳实践。

回调函数基础

回调函数是 JavaScript 异步编程的基础模式:

function fetchData(callback) {
  setTimeout(() => {
    const data = { id: 1, name: "John" };
    callback(null, data); // Node.js 风格:错误优先回调
  }, 1000);
}

// 使用回调
fetchData((err, data) => {
  if (err) console.error(err);
  else console.log("回调结果:", data);
});

回调转 Promise 的三种方法

     方法1:手动创建 Promise 包装器

function fetchDataPromise() {
  return new Promise((resolve, reject) => {
    fetchData((err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// 使用 Promise
fetchDataPromise()
  .then(data => console.log("Promise.then 结果:", data))
  .catch(err => console.error(err));

     方法2:使用 util.promisify (Node.js)

const util = require('util');

// 自动转换错误优先回调
const fetchDataPromise = util.promisify(fetchData);

// 使用方式相同
fetchDataPromise()
  .then(data => console.log("util.promisify 结果:", data));

     方法3:自定义 promisify 函数

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}

// 使用自定义 promisify
const customPromisified = promisify(fetchData);
customPromisified()
  .then(data => console.log("自定义 promisify 结果:", data));

回调转 async/await

     基本转换模式

async function fetchDataAsync() {
  try {
    const data = await fetchDataPromise(); // 使用 Promise 版本
    console.log("async/await 结果:", data);
    return data;
  } catch (err) {
    console.error("错误处理:", err);
    throw err; // 可选择重新抛出错误
  }
}

// 调用 async 函数
fetchDataAsync();

     多层回调转 async/await

// 原始回调地狱
function getUserData(userId, callback) {
  getUser(userId, (err, user) => {
    if (err) return callback(err);
    getProfile(user.id, (err, profile) => {
      if (err) return callback(err);
      getPosts(user.id, (err, posts) => {
        if (err) return callback(err);
        callback(null, { user, profile, posts });
      });
    });
  });
}

// 转换为 async/await
async function getUserDataAsync(userId) {
  try {
    const user = await getUserPromise(userId);
    const profile = await getProfilePromise(user.id);
    const posts = await getPostsPromise(user.id);
    return { user, profile, posts };
  } catch (err) {
    console.error("获取用户数据失败:", err);
    throw err;
  }
}

转换模式比较

特性回调函数Promiseasync/await
可读性★☆☆☆☆★★★☆☆★★★★★
错误处理手动检查.catch()try/catch
流程控制困难Promise.all/raceawait + Promise.all
调试体验困难较好优秀
代码结构嵌套链式线性
学习曲线简单中等容易

高级转换技巧

     1. 处理多个回调参数

// 原始回调(多个成功参数)
function getCoordinates(callback) {
  callback(null, 40.7128, 74.0060); // 经度, 纬度
}

// 转换方案
function getCoordinatesPromise() {
  return new Promise((resolve, reject) => {
    getCoordinates((err, ...coords) => {
      if (err) reject(err);
      else resolve(coords); // 返回数组 [40.7128, 74.0060]
    });
  });
}

// 使用
async function displayCoords() {
  const [lat, lon] = await getCoordinatesPromise();
  console.log(`纬度: ${lat}, 经度: ${lon}`);
}

     2. 转换事件发射器

// 转换事件监听为 Promise
function eventToPromise(emitter, event) {
  return new Promise(resolve => {
    emitter.once(event, resolve);
  });
}

// 使用示例
async function waitForEvent() {
  const result = await eventToPromise(document, 'DOMContentLoaded');
  console.log('DOM已加载');
}

     3. 超时控制

// 添加超时功能的 Promise 包装器
function withTimeout(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error("操作超时")), timeout)
    )
  ]);
}

// 使用
async function fetchWithTimeout() {
  try {
    return await withTimeout(fetchDataPromise(), 2000);
  } catch (err) {
    if (err.message === "操作超时") {
      console.log("请求超时,使用缓存数据");
      return cachedData;
    }
    throw err;
  }
}

     4. 进度通知

// 带进度报告的回调转 Promise
function processWithProgress(callback) {
  callback(null, 0); // 初始进度
  setTimeout(() => callback(null, 50), 500);
  setTimeout(() => callback(null, 100), 1000);
}

function processWithProgressPromise() {
  return new Promise((resolve, reject) => {
    const progressHandler = (err, progress) => {
      if (err) reject(err);
      else if (progress === 100) resolve();
      else console.log(`进度: ${progress}%`);
    };
    
    processWithProgress(progressHandler);
  });
}

// 使用
processWithProgressPromise()
  .then(() => console.log("处理完成"));

实际应用示例

     文件操作转换 (Node.js)

const fs = require('fs');

// 回调版本
fs.readFile('data.txt', 'utf8', (err, content) => {
  if (err) console.error(err);
  else console.log(content);
});

// Promise 版本
const readFilePromise = util.promisify(fs.readFile);

// async/await 版本
async function readAndProcess() {
  try {
    const content = await readFilePromise('data.txt', 'utf8');
    const processed = content.toUpperCase();
    await writeFilePromise('output.txt', processed);
    console.log("文件处理完成");
  } catch (err) {
    console.error("文件操作失败:", err);
  }
}

     AJAX 请求转换

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

// 转为 Promise
function ajaxGetPromise(url) {
  return new Promise((resolve, reject) => {
    ajaxGet(url, (err, data) => {
      if (err) reject(err);
      else resolve(JSON.parse(data));
    });
  });
}

// async/await 使用
async function loadUserData() {
  try {
    const user = await ajaxGetPromise('/api/user');
    const posts = await ajaxGetPromise(`/api/posts?userId=${user.id}`);
    return { user, posts };
  } catch (err) {
    console.error("数据加载失败", err);
    return { user: null, posts: [] };
  }
}

最佳实践与常见陷阱

     最佳实践

  1. 统一错误处理:始终使用 catch 或 try/catch
  2. 避免冗余:不需要在 async 函数中包装已经返回 Promise 的函数
  3. 并行优化:使用 Promise.all 处理独立操作
// 顺序执行(慢)
async function sequential() {
  const a = await fetchA();
  const b = await fetchB();
  return [a, b];
}

// 并行执行(快)
async function parallel() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  return [a, b];
}

     常见陷阱及解决方案

  1. 忘记 await
// 错误:忘记 await
async function example() {
  const data = fetchDataPromise(); // 缺少 await
  console.log(data); // 输出 Promise 对象
}

// 正确
async function example() {
  const data = await fetchDataPromise();
  console.log(data); // 实际数据
}
  1. 循环中的异步处理
// 错误:在 forEach 中使用 await
async function processArray(array) {
  array.forEach(async item => {
    await processItem(item); // 不会按预期等待
  });
}

// 正确:使用 for...of
async function processArray(array) {
  for (const item of array) {
    await processItem(item); // 顺序执行
  }
}

// 并行处理
async function processArrayParallel(array) {
  await Promise.all(array.map(item => processItem(item)));
}
  1. 错误处理位置不当
// 错误:catch 位置错误
async function risky() {
  try {
    return fetchDataPromise();
  } catch (err) {
    // 不会捕获 fetchDataPromise 中的错误
  }
}

// 正确
async function safe() {
  try {
    return await fetchDataPromise(); // 需要 await
  } catch (err) {
    // 正确捕获错误
  }
}

总结

将回调函数转换为 Promise 和 async/await 是现代 JavaScript 开发的关键技能:

  1. 回调转 Promise

    • 手动创建 Promise 包装器
    • 使用 util.promisify (Node.js)
    • 自定义 promisify 函数
  2. Promise 转 async/await

    • 使用 async 函数声明
    • 用 await 替代 .then()
    • 用 try/catch 处理错误
  3. 高级技巧

    • 处理多参数回调
    • 添加超时控制
    • 实现进度报告
    • 转换事件发射器
  4. 最佳实践

    • 优先使用 async/await 提升可读性
    • 结合 Promise.all 优化并行操作
    • 始终实现错误处理
    • 避免常见陷阱如忘记 await

通过掌握这些转换技巧,你可以将遗留的回调风格代码现代化,构建更清晰、更易维护的异步 JavaScript 应用。

















4️⃣ 参考 4

JavaScript 回调函数转 Promise 和 Async/Await 指南

将回调风格的异步代码转换为 Promise 和 Async/Await 模式是现代 JavaScript 开发的必备技能。以下是详细转换方法和最佳实践:

一、回调函数转 Promise 基础模式

     1. 标准回调转 Promise

// 原始回调函数
function fetchData(callback) {
  setTimeout(() => {
    const data = { id: 1, name: "示例数据" };
    callback(null, data); // Node.js 风格 (error-first)
  }, 1000);
}

// 转换为 Promise
function fetchDataPromise() {
  return new Promise((resolve, reject) => {
    fetchData((err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// 使用方式
fetchDataPromise()
  .then(data => console.log(data))
  .catch(err => console.error(err));

     2. 带参数的转换

// 原始带参回调
function getUser(id, callback) { ... }

// Promise 封装
function getUserPromise(id) {
  return new Promise((resolve, reject) => {
    getUser(id, (err, user) => {
      err ? reject(err) : resolve(user);
    });
  });
}

二、高级转换技巧

     1. 自动转换多个回调函数

const promisify = (fn) => (...args) => 
  new Promise((resolve, reject) => {
    fn(...args, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });

// 使用示例
const readFilePromise = promisify(fs.readFile);

     2. Node.js 内置工具

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

// 自动转换
const readFile = util.promisify(fs.readFile);

// 使用
readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

三、Promise 转 Async/Await

     1. 基本转换

// Promise 版本
function loadData() {
  return fetchDataPromise()
    .then(data => processData(data))
    .then(result => saveResult(result))
    .catch(err => handleError(err));
}

// Async/Await 版本
async function loadData() {
  try {
    const data = await fetchDataPromise();
    const processed = await processData(data);
    return await saveResult(processed);
  } catch (err) {
    handleError(err);
  }
}

     2. 并行请求优化

// Promise.all 并行处理
async function loadAll() {
  try {
    const [user, orders] = await Promise.all([
      getUserPromise(123),
      getOrdersPromise(123)
    ]);
    return { user, orders };
  } catch (err) {
    console.error("加载失败", err);
  }
}

四、特殊场景处理

     1. 事件监听器转 Promise

function eventToPromise(emitter, event) {
  return new Promise((resolve) => {
    emitter.once(event, resolve);
  });
}

// 使用
async function waitForEvent() {
  const data = await eventToPromise(socket, 'data');
  console.log('收到数据', data);
}

     2. 超时控制

function withTimeout(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error("请求超时")), timeout)
    )
  ]);
}

// 使用
async function fetchWithTimeout() {
  try {
    const data = await withTimeout(fetchDataPromise(), 3000);
    console.log(data);
  } catch (err) {
    console.error(err.message); // "请求超时"
  }
}

五、最佳实践与陷阱规避

  1. 避免 await 滥用

    // 反模式 (顺序执行无依赖操作)
    const a = await getA(); // 不必要等待
    const b = await getB();
    
    // 正确做法
    const [a, b] = await Promise.all([getA(), getB()]);
    
  2. 错误处理

    // 危险写法 (未捕获异常)
    async function risky() {
      const data = await fetchData(); // 可能抛出异常
    }
    
    // 安全写法
    async function safe() {
      try {
        const data = await fetchData();
      } catch (err) {
        console.error("安全捕获", err);
      }
    }
    
  3. 混合模式转换

    // 回调 → Promise → Async/Await
    async function fullConversion() {
      try {
        const user = await new Promise((resolve, reject) => {
          legacyGetUser(123, (err, data) => {
            err ? reject(err) : resolve(data);
          });
        });
        
        return user;
      } catch (err) {
        // 统一错误处理
      }
    }
    

六、转换对照表

模式回调函数PromiseAsync/Await
基本结构func(arg, (err, data) => {...})funcPromise(arg).then().catch()const data = await funcPromise(arg)
错误处理if(err) 检查.catch()try/catch
并行执行手动计数器Promise.allawait Promise.all
结果传递嵌套回调return 链式同步赋值
可读性★☆☆☆☆★★★☆☆★★★★★

总结建议

  1. 新项目:直接使用 Async/Await + Promise 组合
  2. 旧代码改造
    • 使用 util.promisify 转换核心模块
    • 自定义 promisify 函数转换第三方库
  3. 关键位置
    • 始终使用 try/catch 包裹 await
    • 并行操作使用 Promise.all
    • 超时控制使用 Promise.race

通过系统性地将回调转换为 Promise 和 Async/Await,可显著提升代码可读性、可维护性和错误处理能力,是现代 JavaScript 开发的必备技能。

















5️⃣ 参考 5

JavaScript 回调函数转 Promise 和 async/await 完全指南

     回调函数基础模式 典型的 Node.js 风格回调(error-first 回调):

function asyncTask(arg, callback) {
  setTimeout(() => {
    if (Math.random() > 0.8) {
      callback(new Error('Random failure'), null);
    } else {
      callback(null, `Result for ${arg}`);
    }
  }, 1000);
}

     转换方法 1:手动封装为 Promise

function promisifyAsyncTask(arg) {
  return new Promise((resolve, reject) => {
    asyncTask(arg, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
}

     转换方法 2:使用 Node.js 的 util.promisify

const util = require('util');

// 单个函数转换
const promisifiedTask = util.promisify(asyncTask);

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

     转换方法 3:通用 promisify 函数

function promisify(originalFn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      originalFn(...args, (error, ...results) => {
        if (error) {
          reject(error);
        } else {
          // 处理多个返回值的回调
          resolve(results.length > 1 ? results : results[0]);
        }
      });
    });
  };
}

const asyncTaskPromise = promisify(asyncTask);

     使用方法对比

         原始回调方式

asyncTask('input', (err, result) => {
  if (err) {
    console.error('Error:', err.message);
    return;
  }
  console.log('Result:', result);
});

         Promise.then() 方式

promisifyAsyncTask('input')
  .then(result => {
    console.log('Result:', result);
  })
  .catch(err => {
    console.error('Error:', err.message);
  })
  .finally(() => {
    console.log('Operation completed');
  });

         async/await 方式

async function runTask() {
  try {
    const result = await promisifyAsyncTask('input');
    console.log('Result:', result);
  } catch (err) {
    console.error('Error:', err.message);
  } finally {
    console.log('Operation completed');
  }
}

runTask();

     特殊场景处理

         1. 多参数回调转换

function multiArgFunc(callback) {
  setTimeout(() => {
    callback(null, 'data1', 'data2', 'data3');
  }, 1000);
}

// 转换方法
const promisifiedMulti = promisify(multiArgFunc);

// 使用
promisifiedMulti()
  .then(results => {
    const [data1, data2, data3] = results;
    console.log(data1, data2, data3);
  });

         2. 对象方法转换

const apiClient = {
  fetchData: function(id, callback) {
    setTimeout(() => {
      callback(null, `Data for ${id}`);
    }, 500);
  }
};

// 绑定上下文
const fetchDataAsync = util.promisify(apiClient.fetchData).bind(apiClient);

// 使用
fetchDataAsync(123)
  .then(data => console.log(data));

         3. 事件发射器转换

const { EventEmitter } = require('events');

function eventToPromise(emitter, eventName, errorEvent = 'error') {
  return new Promise((resolve, reject) => {
    emitter.once(eventName, resolve);
    emitter.once(errorEvent, reject);
  });
}

// 使用示例
const emitter = new EventEmitter();
setTimeout(() => emitter.emit('data', 'Event data'), 1000);

eventToPromise(emitter, 'data')
  .then(data => console.log('Received:', data));

     高级转换技巧

         1. 回调超时控制

function withTimeout(promise, timeoutMs, errorMsg = 'Operation timed out') {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error(errorMsg)), timeoutMs)
    )
  ]);
}

// 使用
withTimeout(promisifyAsyncTask('input'), 1500)
  .then(console.log)
  .catch(err => console.error(err.message));

         2. 重试机制

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); // 指数退避
  }
}

// 使用
retry(() => promisifyAsyncTask('input'))
  .then(console.log)
  .catch(console.error);

     转换模式对比

转换方式优点缺点适用场景
手动封装完全控制转换逻辑需要手动编写简单转换或特殊需求
util.promisify官方标准方法仅支持标准回调格式Node.js 环境
通用 promisify灵活处理多种情况需要自定义实现复杂回调结构
事件转换处理事件驱动API需要手动管理事件EventEmitter 相关API

     最佳实践

  1. 错误处理统一

    // 好:集中错误处理
    async function main() {
      try {
        const data = await readFileAsync('file.txt');
        const processed = await processData(data);
        await saveData(processed);
      } catch (err) {
        console.error('Process failed:', err);
      }
    }
    
  2. 避免混合模式

    // 避免:混合回调和Promise
    function antiPattern() {
      asyncTask('input', (err, result) => {
        if (err) return console.error(err);
        promisifiedTask(result)
          .then(console.log)
          .catch(console.error);
      });
    }
    
  3. 并行优化

    // 顺序执行(慢)
    const result1 = await task1();
    const result2 = await task2();
    
    // 并行执行(快)
    const [result1, result2] = await Promise.all([task1(), task2()]);
    
  4. 资源清理

    let resource;
    try {
      resource = await acquireResource();
      await useResource(resource);
    } finally {
      if (resource) await releaseResource(resource);
    }
    

     浏览器环境兼容方案

<!-- 旧浏览器支持 -->
<script>
  // 简易 promisify 实现
  if (typeof util === 'undefined') {
    window.util = {
      promisify: function(fn) {
        return function(...args) {
          return new Promise((resolve, reject) => {
            fn(...args, (err, ...results) => {
              err ? reject(err) : resolve(results.length > 1 ? results : results[0]);
            });
          });
        };
      }
    };
  }
  
  // 使用
  const asyncTaskPromise = util.promisify(asyncTask);
</script>

     完整转换示例

// 原始回调函数
function databaseQuery(query, callback) {
  setTimeout(() => {
    if (query.includes('DROP')) {
      callback(new Error('Invalid query'), null);
    } else {
      callback(null, { query, results: [1, 2, 3] });
    }
  }, 500);
}

// 转换为Promise
const queryAsync = util.promisify(databaseQuery);

// 使用async/await
async function runQueries() {
  try {
    const result1 = await queryAsync('SELECT * FROM users');
    console.log('Result 1:', result1);
    
    const result2 = await queryAsync('SELECT * FROM products');
    console.log('Result 2:', result2);
    
    return [result1, result2];
  } catch (err) {
    console.error('Database error:', err.message);
    throw err;
  }
}

// 执行
runQueries()
  .then(results => console.log('All queries completed'))
  .catch(() => console.log('Process terminated'));

通过将回调函数转换为 Promise 和 async/await 模式,可以显著提升代码的可读性、可维护性和错误处理能力。这种转换是现代 JavaScript 开发中的必备技能,特别在处理复杂异步流程时效果显著。

















6️⃣ 参考 6

JavaScript 回调函数转 Promise 与 async/await 完全指南

一、回调函数的问题与解决方案

     回调地狱示例

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

     解决方案对比

方案代码风格可读性错误处理
回调函数嵌套结构手动检查错误
Promise.then()链式调用良好.catch() 方法
async/await同步风格优秀try/catch 块

二、回调转 Promise 的核心方法

     1. 手动转换基础模式

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

     2. Node.js 的 util.promisify

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

// 转换单个函数
const readFile = util.promisify(fs.readFile);

// 转换整个模块
const fsPromises = Object.fromEntries(
  Object.entries(fs).map(([key, value]) => [
    key, 
    typeof value === 'function' ? util.promisify(value) : value
  ])
);

     3. 浏览器环境转换

// 转换 IndexedDB 请求
function promisifyRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

// 使用
async function getDataFromDB() {
  const request = db.transaction('store').objectStore('store').get('key');
  return promisifyRequest(request);
}

三、Promise.then() 方式使用

     1. 基本使用模式

// 转换回调函数
const getUserPromise = promisify(getUser);

// 使用 Promise.then()
getUserPromise(userId)
  .then(user => getOrdersPromise(user.id))
  .then(orders => getOrderDetailsPromise(orders[0].id))
  .then(renderUI)
  .catch(error => {
    console.error('处理失败:', error);
  });

     2. 链式调用技巧

// 值传递
getConfig()
  .then(config => {
    console.log('配置加载完成');
    return initializeApp(config); // 返回新Promise
  })
  .then(app => {
    console.log('应用初始化完成');
    return app.start(); // 返回另一个Promise
  })
  .then(() => {
    console.log('应用启动完成');
  });

// 并行处理
Promise.all([
  fetchUserData(),
  fetchProductList(),
  fetchNewsFeed()
])
.then(([user, products, news]) => {
  console.log('所有数据加载完成');
  renderDashboard(user, products, news);
});

     3. 高级错误处理

apiRequest()
  .then(handleResponse)
  .catch(error => {
    if (error.statusCode === 404) {
      return fallbackData(); // 恢复处理
    }
    throw error; // 重新抛出错误
  })
  .then(data => {
    // 正常或恢复后的处理
  })
  .catch(finalError => {
    console.error('最终错误处理:', finalError);
  });

四、async/await 方式使用

     1. 基本转换模式

async function getUserOrders(userId) {
  try {
    const user = await getUserPromise(userId);
    const orders = await getOrdersPromise(user.id);
    const details = await getOrderDetailsPromise(orders[0].id);
    renderUI(details);
    return details;
  } catch (error) {
    console.error('获取订单失败:', error);
    return null;
  }
}

     2. 并行执行优化

async function loadDashboard() {
  // 同时启动所有请求
  const [user, products, news] = await Promise.all([
    fetchUserPromise(),
    fetchProductsPromise(),
    fetchNewsPromise()
  ]);
  
  // 顺序处理依赖数据
  const recommendations = await getRecommendations(user.interests);
  
  return { user, products, news, recommendations };
}

     3. 复杂控制流

async function processCheckout(userId) {
  const user = await getUserPromise(userId);
  
  if (!user.verified) {
    throw new Error('用户未验证');
  }
  
  const [cart, address] = await Promise.all([
    getCartPromise(user.id),
    getAddressPromise(user.id)
  ]);
  
  for (const item of cart.items) {
    if (!item.inStock) {
      await sendNotification(`商品 ${item.name} 缺货`);
      item.status = 'backorder';
    }
  }
  
  const payment = await processPayment(user, cart.total);
  await sendConfirmation(user, payment);
  
  return payment;
}

五、特殊回调场景处理

     1. 事件监听器转 Promise

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

// 使用
async function handleButtonClick() {
  const button = document.getElementById('submit');
  const clickEvent = await eventToPromise(button, 'click');
  console.log('按钮被点击', clickEvent);
}

     2. 带进度报告的回调

function processWithProgress(callback) {
  let progress = 0;
  const interval = setInterval(() => {
    progress += 10;
    callback(null, progress);
    if (progress >= 100) {
      clearInterval(interval);
      callback(null, '完成');
    }
  }, 200);
}

// 转换为Promise + 进度回调
function promisifyWithProgress(fn) {
  return {
    promise: new Promise((resolve, reject) => {
      fn((err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    }),
    onProgress: (callback) => {
      fn((err, progress) => {
        if (!err && typeof progress === 'number') callback(progress);
      });
    }
  };
}

// 使用
const processor = promisifyWithProgress(processWithProgress);
processor.onProgress(progress => {
  console.log(`进度: ${progress}%`);
});

processor.promise
  .then(result => console.log('结果:', result))
  .catch(error => console.error('错误:', error));

     3. 可取消的 Promise

function cancellablePromise(fn) {
  let cancel;
  const promise = new Promise((resolve, reject) => {
    const cancelToken = { isCancelled: false };
    
    cancel = () => {
      cancelToken.isCancelled = true;
      reject(new Error('操作已取消'));
    };
    
    fn(resolve, reject, cancelToken);
  });
  
  return { promise, cancel };
}

// 使用
const { promise, cancel } = cancellablePromise((resolve, reject, token) => {
  const timer = setTimeout(() => {
    if (!token.isCancelled) resolve('操作完成');
  }, 5000);
  
  token.onCancel = () => clearTimeout(timer);
});

// 取消操作
cancelButton.addEventListener('click', cancel);

六、实战应用示例

     1. 文件上传处理

// 回调版本
function uploadFile(file, onProgress, onComplete, onError) {
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  formData.append('file', file);
  
  xhr.upload.addEventListener('progress', (e) => {
    if (e.lengthComputable) {
      const percent = Math.round((e.loaded / e.total) * 100);
      onProgress(percent);
    }
  });
  
  xhr.addEventListener('load', () => {
    if (xhr.status === 200) {
      onComplete(xhr.responseText);
    } else {
      onError(new Error('上传失败'));
    }
  });
  
  xhr.addEventListener('error', () => onError(new Error('网络错误')));
  xhr.open('POST', '/upload');
  xhr.send(formData);
}

// Promise + async/await 版本
function uploadFileAsync(file) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);
    
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = Math.round((e.loaded / e.total) * 100);
        // 可以分发进度事件
      }
    });
    
    xhr.addEventListener('load', () => {
      if (xhr.status === 200) {
        resolve(xhr.responseText);
      } else {
        reject(new Error('上传失败'));
      }
    });
    
    xhr.addEventListener('error', () => reject(new Error('网络错误')));
    xhr.open('POST', '/upload');
    xhr.send(formData);
  });
}

// 使用
async function handleUpload() {
  try {
    const file = document.getElementById('fileInput').files[0];
    const result = await uploadFileAsync(file);
    console.log('上传成功:', result);
  } catch (error) {
    console.error('上传失败:', error);
  }
}

     2. 数据库操作序列

// 回调版本
db.connect((err, connection) => {
  if (err) return handleError(err);
  
  connection.query('SELECT * FROM users', (err, users) => {
    if (err) return handleError(err);
    
    connection.query('SELECT * FROM orders', (err, orders) => {
      if (err) return handleError(err);
      
      connection.close();
      processData(users, orders);
    });
  });
});

// async/await 版本
async function fetchData() {
  const connection = await db.connectAsync();
  
  try {
    const [users, orders] = await Promise.all([
      connection.queryAsync('SELECT * FROM users'),
      connection.queryAsync('SELECT * FROM orders')
    ]);
    
    return { users, orders };
  } finally {
    connection.close();
  }
}

// 使用
fetchData()
  .then(({ users, orders }) => {
    console.log('数据获取完成');
    renderUI(users, orders);
  })
  .catch(error => {
    console.error('数据库错误:', error);
  });

七、最佳实践与常见陷阱

     1. 最佳实践指南

  1. 统一错误处理:始终使用 .catch() 或 try/catch
  2. 避免混合风格:不要混用回调和 Promise
  3. 资源清理:使用 finally 块释放资源
  4. 命名约定:区分回调函数和 Promise 函数
    // 回调函数
    function getUser(userId, callback) { ... }
    
    // Promise 版本
    function getUserAsync(userId) { ... }
    
  5. 性能优化:并行执行独立操作

     2. 常见陷阱及解决方案

陷阱错误示例解决方案
忘记 awaitsaveData(data);await saveData(data);
Promise 未处理拒绝fetch('/api');添加错误处理
并发控制缺失await Promise.all(1000个请求)限制并发数
资源未释放缺少 finally 块使用 try/finally
回调多次调用回调函数被多次执行确保回调只执行一次

     3. 调试技巧

// 添加异步堆栈追踪
async function debugAsync() {
  // 创建带标记的错误
  const error = new Error('调试点');
  Error.captureStackTrace(error, debugAsync);
  
  try {
    await someOperation();
  } catch (err) {
    // 添加原始堆栈
    err.stack += `\n--- 异步操作起点 ---\n${error.stack}`;
    throw err;
  }
}

// 使用长堆栈追踪工具
import 'longjohn'; // Node.js 长堆栈追踪

八、总结

     回调转 Promise 的完整流程

graph TD
    A[识别回调函数] --> B{判断回调模式}
    B -->|错误优先| C[使用 promisify 转换]
    B -->|事件监听| D[创建 Promise 包装]
    C --> E[获取 Promise 版本]
    D --> E
    E --> F{使用方式}
    F --> G[Promise.then 链]
    F --> H[async/await]
    G --> I[错误处理 .catch]
    H --> J[错误处理 try/catch]
    I --> K[最终处理 .finally]
    J --> K

     技术选择指南

场景推荐方案
简单转换util.promisify
复杂回调手动 Promise 包装
顺序操作async/await
并行操作Promise.all
事件处理自定义 Promise 包装
进度报告Promise + 回调混合

     核心价值

  1. 可读性提升:消除回调嵌套,代码更线性
  2. 错误处理简化:统一使用 try/catch 机制
  3. 控制流优化:轻松实现复杂异步逻辑
  4. 现代开发兼容:无缝对接 async/await 语法
  5. 性能提升:更高效的并行处理能力

通过将回调函数转换为 Promise 和 async/await,您可以:

  • 将嵌套的回调结构转换为线性的同步风格代码
  • 使用 try/catch 统一处理同步和异步错误
  • 利用 Promise.all 实现高效的并行操作
  • 提高代码的可维护性和可读性
  • 为现代 JavaScript 开发铺平道路
// 转换后的优雅代码示例
async function fullProcess(userId) {
  try {
    const user = await getUserAsync(userId);
    const [orders, messages] = await Promise.all([
      getOrdersAsync(user.id),
      getMessagesAsync(user.id)
    ]);
    
    await sendNotificationAsync(user);
    return { user, orders, messages };
  } catch (error) {
    console.error('处理失败:', error);
    return { error: error.message };
  } finally {
    cleanupResources();
  }
}

掌握回调转 Promise 和 async/await 的技巧,将使您的 JavaScript 代码更健壮、更易维护,并能充分利用现代 JavaScript 的强大功能。

















7️⃣ 参考 7

JavaScript 回调函数转 Promise 和 async/await 完全指南

在 JavaScript 异步编程中,从回调函数到 Promise 再到 async/await 的转换是现代开发的核心技能。下面我将详细展示如何将传统的回调函数转换为 Promise 和 async/await 形式。

一、回调函数基础模式

     典型回调模式 (Node.js 风格)

function getUserData(userId, callback) {
  setTimeout(() => {
    if (Math.random() > 0.2) { // 80% 成功率
      callback(null, { id: userId, name: '张三' });
    } else {
      callback(new Error('用户不存在'), null);
    }
  }, 1000);
}

// 使用回调
getUserData(123, (err, user) => {
  if (err) {
    console.error('错误:', err.message);
    return;
  }
  console.log('用户数据:', user);
});

二、回调转 Promise

     手动封装为 Promise

function getUserDataPromise(userId) {
  return new Promise((resolve, reject) => {
    getUserData(userId, (err, user) => {
      if (err) {
        reject(err);
      } else {
        resolve(user);
      }
    });
  });
}

// 使用 Promise
getUserDataPromise(123)
  .then(user => console.log('用户数据:', user))
  .catch(err => console.error('错误:', err.message));

     Node.js util.promisify 方法

const util = require('util');

// 自动转换回调风格的函数
const getUserDataPromise = util.promisify(getUserData);

// 使用方式相同
getUserDataPromise(123)
  .then(console.log)
  .catch(console.error);

     浏览器环境下的通用 promisify 函数

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      });
    });
  };
}

// 使用
const getUserDataPromise = promisify(getUserData);
getUserDataPromise(123).then(console.log).catch(console.error);

三、Promise 转 async/await

     基本转换

async function getUserDataAsync(userId) {
  try {
    const user = await getUserDataPromise(userId);
    console.log('用户数据:', user);
    return user;
  } catch (err) {
    console.error('错误:', err.message);
    throw err; // 可选择重新抛出错误
  }
}

// 使用
getUserDataAsync(123);

     直接转换回调为 async/await

function getUserDataAsync(userId) {
  return new Promise((resolve, reject) => {
    getUserData(userId, (err, user) => {
      if (err) reject(err);
      else resolve(user);
    });
  });
}

// 使用
(async () => {
  try {
    const user = await getUserDataAsync(123);
    console.log('用户数据:', user);
  } catch (err) {
    console.error('错误:', err.message);
  }
})();

四、复杂回调场景转换

     1. 多个依赖回调转 Promise

// 原始回调嵌套
function getUserProfile(userId, callback) {
  getUserData(userId, (err, user) => {
    if (err) return callback(err);
    
    getProfile(user.id, (err, profile) => {
      if (err) return callback(err);
      
      callback(null, { ...user, ...profile });
    });
  });
}

// 转换为 Promise
function getUserProfilePromise(userId) {
  return getUserDataPromise(userId)
    .then(user => getProfilePromise(user.id)
    .then(profile => ({ ...user, profile }));
}

// 转换为 async/await
async function getUserProfileAsync(userId) {
  const user = await getUserDataPromise(userId);
  const profile = await getProfilePromise(user.id);
  return { ...user, ...profile };
}

     2. 并行操作回调转 Promise.all

// 原始回调
function getDashboardData(userId, callback) {
  let user, orders;
  let completed = 0;
  const checkCompletion = () => {
    if (++completed === 2) {
      callback(null, { user, orders });
    }
  };

  getUserData(userId, (err, result) => {
    if (err) return callback(err);
    user = result;
    checkCompletion();
  });

  getOrders(userId, (err, result) => {
    if (err) return callback(err);
    orders = result;
    checkCompletion();
  });
}

// 转换为 Promise
function getDashboardDataPromise(userId) {
  return Promise.all([
    getUserDataPromise(userId),
    getOrdersPromise(userId)
  ]).then(([user, orders]) => ({ user, orders }));
}

// 转换为 async/await
async function getDashboardDataAsync(userId) {
  const [user, orders] = await Promise.all([
    getUserDataPromise(userId),
    getOrdersPromise(userId)
  ]);
  return { user, orders };
}

     3. 事件监听器转 Promise

// 原始事件监听
function waitForEvent(element, eventType) {
  return new Promise((resolve) => {
    const handler = (event) => {
      element.removeEventListener(eventType, handler);
      resolve(event);
    };
    element.addEventListener(eventType, handler);
  });
}

// 使用
(async () => {
  const button = document.getElementById('myButton');
  const clickEvent = await waitForEvent(button, 'click');
  console.log('按钮被点击了!', clickEvent);
})();

五、特殊回调模式转换

     1. 超时控制

function withTimeout(promise, timeout) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('操作超时'));
    }, timeout);

    promise
      .then(result => {
        clearTimeout(timer);
        resolve(result);
      })
      .catch(err => {
        clearTimeout(timer);
        reject(err);
      });
  });
}

// 使用
(async () => {
  try {
    const user = await withTimeout(getUserDataAsync(123), 2000);
    console.log('用户数据:', user);
  } catch (err) {
    console.error('错误:', err.message);
  }
})();

     2. 进度回调转 Promise

function processWithProgress(callback) {
  return new Promise((resolve, reject) => {
    let progress = 0;
    
    const interval = setInterval(() => {
      progress += 10;
      callback('progress', progress);
      
      if (progress >= 100) {
        clearInterval(interval);
        resolve('处理完成');
      }
    }, 200);
  });
}

// 使用
processWithProgress((type, value) => {
  if (type === 'progress') {
    console.log(`进度: ${value}%`);
  }
}).then(result => console.log(result));

六、最佳实践与注意事项

     转换规则总结

回调模式Promise 转换async/await 转换
错误优先回调new Promise + 手动处理await + try/catch
多个依赖回调Promise 链 (.then())顺序 await
并行回调Promise.all()await Promise.all()
事件监听器包装为一次性 Promiseawait 事件 Promise
进度回调保留回调参数保留回调参数

     常见错误处理

  1. 未捕获的 Promise 拒绝

    // 添加全局未处理拒绝监听器
    process.on('unhandledRejection', (reason, promise) => {
      console.error('未处理的Promise拒绝:', reason);
    });
    
    // 浏览器环境
    window.addEventListener('unhandledrejection', event => {
      console.error('未处理的Promise拒绝:', event.reason);
      event.preventDefault();
    });
    
  2. 正确处理 finally

    fetchData()
      .then(data => process(data))
      .catch(err => console.error(err))
      .finally(() => {
        // 无论成功失败都会执行
        cleanupResources();
      });
    

     性能优化技巧

  1. 避免不必要的 await

    // ❌ 不必要顺序执行
    async function process() {
      const a = await getA();
      const b = await getB(); // 等待a完成才开始b
    }
    
    // ✅ 并行执行
    async function process() {
      const [a, b] = await Promise.all([getA(), getB()]);
    }
    
  2. 限制并发数

    async function throttle(tasks, limit) {
      const results = [];
      const executing = [];
      
      for (const task of tasks) {
        const p = task().then(r => {
          executing.splice(executing.indexOf(p), 1);
          return r;
        });
        
        executing.push(p);
        results.push(p);
        
        if (executing.length >= limit) {
          await Promise.race(executing);
        }
      }
      
      return Promise.all(results);
    }
    

七、现代 JavaScript 的完整异步流程

graph TD
    A[回调函数] --> B[使用 promisify 转换]
    B --> C[Promise]
    C --> D[使用 async/await 调用]
    D --> E[错误处理 try/catch]
    E --> F[清理操作 finally]
    
    B --> G[直接创建 Promise]
    G --> D

     完整转换示例

// 1. 原始回调函数
function legacyApi(data, callback) {
  setTimeout(() => {
    if (Math.random() > 0.3) {
      callback(null, `处理结果: ${data}`);
    } else {
      callback(new Error('处理失败'), null);
    }
  }, 500);
}

// 2. 转换为 Promise
const promisifiedApi = util.promisify(legacyApi);

// 3. 使用 async/await
async function modernWorkflow(data) {
  try {
    console.log('开始处理...');
    const result = await promisifiedApi(data);
    
    console.log('处理结果:', result);
    return result;
  } catch (err) {
    console.error('处理失败:', err.message);
    throw err;
  } finally {
    console.log('清理资源...');
  }
}

// 4. 执行
modernWorkflow('测试数据')
  .then(() => console.log('工作流完成'))
  .catch(() => console.log('工作流失败'));

总结

从回调函数到 Promise 再到 async/await 的转换是 JavaScript 异步编程的进化之路:

  1. 回调函数转 Promise

    • 使用 new Promise 手动封装
    • 使用 Node.js 的 util.promisify
    • 自定义 promisify 函数
  2. Promise 转 async/await

    • 使用 async 函数包装
    • 使用 await 处理 Promise
    • 使用 try/catch/finally 处理流程
  3. 复杂场景处理

    • 使用 Promise.all() 处理并行任务
    • 使用 Promise 链处理顺序依赖
    • 特殊模式如事件监听器、进度回调的转换
  4. 最佳实践

    • 始终处理错误
    • 合理使用并行执行
    • 添加全局未处理拒绝监听器

掌握这些转换技巧,可以让你在现代 JavaScript 项目中优雅地处理遗留的回调代码,同时享受 async/await 带来的可读性和可维护性优势。

















8️⃣ 参考 8

JavaScript 回调函数转 Promise 和 async/await 完全指南

一、回调函数的局限性与转换必要性

     回调地狱问题

// 典型的回调地狱
getUser(userId, (userErr, user) => {
  if (userErr) return handleError(userErr);
  
  getOrders(user.id, (ordersErr, orders) => {
    if (ordersErr) return handleError(ordersErr);
    
    getOrderDetails(orders[0].id, (detailsErr, details) => {
      if (detailsErr) return handleError(detailsErr);
      
      renderUI(details);
    });
  });
});

     转换的必要性

  1. 可读性提升:线性代码结构替代嵌套
  2. 错误处理统一:try/catch 替代分散的错误检查
  3. 流程控制简化:更易实现并行、顺序等复杂流程
  4. 现代开发需求:与 async/await 生态无缝集成

二、回调转 Promise 的核心方法

     1. 手动封装 Promise

// 原始回调函数
function readFileCallback(path, callback) {
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) return callback(err);
    callback(null, data);
  });
}

// 转换为 Promise 版本
function readFilePromise(path) {
  return new Promise((resolve, reject) => {
    readFileCallback(path, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// 使用示例
readFilePromise('data.txt')
  .then(data => console.log(data))
  .catch(err => console.error(err));

     2. 使用 util.promisify (Node.js)

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

// 自动转换回调函数
const readFileAsync = util.promisify(fs.readFile);

// 使用示例
readFileAsync('data.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

     3. 处理特殊回调模式

         多个成功参数转数组

function multiArgCallback(a, b, callback) {
  setTimeout(() => callback(null, a * 2, b * 3), 100);
}

function multiArgPromise(a, b) {
  return new Promise((resolve, reject) => {
    multiArgCallback(a, b, (err, ...results) => {
      if (err) reject(err);
      else resolve(results);
    });
  });
}

// 使用
multiArgPromise(2, 3)
  .then(([res1, res2]) => console.log(res1, res2)); // 4, 9

         非错误优先回调

function nonStandardCallback(value, success, failure) {
  setTimeout(() => {
    Math.random() > 0.5 
      ? success(value * 2) 
      : failure('Error occurred');
  }, 100);
}

function nonStandardPromise(value) {
  return new Promise((resolve, reject) => {
    nonStandardCallback(value, resolve, reject);
  });
}

三、Promise 转 async/await

     基本转换模式

// Promise 版本
function getUserData() {
  return fetchUser()
    .then(user => fetchProfile(user.id))
    .then(profile => combineData(profile))
    .catch(err => console.error(err));
}

// async/await 版本
async function getUserData() {
  try {
    const user = await fetchUser();
    const profile = await fetchProfile(user.id);
    return combineData(profile);
  } catch (err) {
    console.error(err);
    return null;
  }
}

     并行操作优化

// 低效顺序执行
async function slowVersion() {
  const user = await fetchUser();
  const orders = await fetchOrders(); // 等待用户完成后才开始
  
  return { user, orders };
}

// 高效并行执行
async function optimizedVersion() {
  // 同时启动异步操作
  const userPromise = fetchUser();
  const ordersPromise = fetchOrders();
  
  // 等待所有结果
  const [user, orders] = await Promise.all([
    userPromise, 
    ordersPromise
  ]);
  
  return { user, orders };
}

四、回调直接转 async/await

     完整转换流程

// 原始回调函数
function processData(input, transform, callback) {
  setTimeout(() => {
    try {
      const result = transform(input);
      callback(null, result);
    } catch (err) {
      callback(err);
    }
  }, 1000);
}

// 步骤1: 转为Promise
function processDataPromise(input, transform) {
  return new Promise((resolve, reject) => {
    processData(input, transform, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

// 步骤2: 使用async/await
async function handleDataProcessing() {
  try {
    const input = await loadInput();
    const result = await processDataPromise(input, x => x * 2);
    console.log('处理结果:', result);
  } catch (err) {
    console.error('处理失败:', err);
  }
}

五、高级转换模式

     1. 回调流转换为异步迭代

// 回调流函数
function createDataStream(callback) {
  let count = 0;
  const interval = setInterval(() => {
    callback(null, count++);
    if (count >= 5) {
      clearInterval(interval);
      callback(null, null); // 结束信号
    }
  }, 500);
}

// 转换为异步生成器
async function* asyncDataStream() {
  let resolve;
  let promise = new Promise(r => resolve = r);
  let ended = false;

  createDataStream((err, data) => {
    if (err) return promise.reject(err);
    if (data === null) {
      ended = true;
      resolve({ done: true });
    } else {
      resolve({ value: data, done: false });
      promise = new Promise(r => resolve = r);
    }
  });

  while (!ended) {
    const next = await promise;
    if (next.done) break;
    yield next.value;
  }
}

// 使用
(async () => {
  for await (const data of asyncDataStream()) {
    console.log('收到数据:', data);
  }
})();

     2. 事件发射器转 Promise

function eventToPromise(emitter, resolveEvent, rejectEvent = 'error') {
  return new Promise((resolve, reject) => {
    const cleanup = () => {
      emitter.off(resolveEvent, onSuccess);
      emitter.off(rejectEvent, onError);
    };

    const onSuccess = (data) => {
      cleanup();
      resolve(data);
    };

    const onError = (err) => {
      cleanup();
      reject(err);
    };

    emitter.on(resolveEvent, onSuccess);
    emitter.on(rejectEvent, onError);
  });
}

// 使用示例
const EventEmitter = require('events');
const emitter = new EventEmitter();

setTimeout(() => emitter.emit('done', '成功!'), 1000);

eventToPromise(emitter, 'done')
  .then(result => console.log(result))
  .catch(err => console.error(err));

六、实际应用场景转换

     场景1:文件处理流程

// 回调版本
fs.readFile('input.txt', 'utf8', (readErr, data) => {
  if (readErr) return console.error(readErr);
  
  processData(data, (processErr, result) => {
    if (processErr) return console.error(processErr);
    
    fs.writeFile('output.txt', result, writeErr => {
      if (writeErr) console.error(writeErr);
      else console.log('完成!');
    });
  });
});

// async/await 版本
async function processFile() {
  try {
    const data = await fs.promises.readFile('input.txt', 'utf8');
    const result = await new Promise((resolve, reject) => {
      processData(data, (err, res) => err ? reject(err) : resolve(res));
    });
    await fs.promises.writeFile('output.txt', result);
    console.log('完成!');
  } catch (err) {
    console.error('处理失败:', err);
  }
}

     场景2:API请求链

// 回调版本
function fetchUserData(userId, callback) {
  getUser(userId, (err, user) => {
    if (err) return callback(err);
    
    getOrders(user.id, (err, orders) => {
      if (err) return callback(err);
      
      getRecommendations(user.id, (err, recommendations) => {
        callback(err, { user, orders, recommendations });
      });
    });
  });
}

// Promise 版本
function fetchUserDataPromise(userId) {
  return getUserPromise(userId)
    .then(user => Promise.all([
      user,
      getOrdersPromise(user.id),
      getRecommendationsPromise(user.id)
    ]))
    .then(([user, orders, recommendations]) => {
      return { user, orders, recommendations };
    });
}

// async/await 版本
async function fetchUserDataAsync(userId) {
  const user = await getUserPromise(userId);
  const [orders, recommendations] = await Promise.all([
    getOrdersPromise(user.id),
    getRecommendationsPromise(user.id)
  ]);
  return { user, orders, recommendations };
}

七、最佳实践与常见陷阱

     最佳实践

  1. 统一错误处理:使用 try/catch 包裹 async 函数

    async function safeOperation() {
      try {
        return await riskyOperation();
      } catch (err) {
        logError(err);
        return fallbackValue;
      }
    }
    
  2. 避免阻塞并行

    // 正确 - 并行执行
    async function parallel() {
      const [a, b] = await Promise.all([taskA(), taskB()]);
    }
    
    // 错误 - 顺序执行
    async function sequential() {
      const a = await taskA(); // 等待完成
      const b = await taskB(); // 才开始执行
    }
    
  3. 合理使用 Promise 工具

    // 使用 Promise.allSettled 处理部分失败
    async function loadMultiple() {
      const results = await Promise.allSettled([
        fetchResource('A'),
        fetchResource('B'),
        fetchResource('C')
      ]);
      
      results.forEach(result => {
        if (result.status === 'fulfilled') {
          useData(result.value);
        } else {
          logError(result.reason);
        }
      });
    }
    

     常见陷阱及解决方案

  1. 忘记 await

    // 错误:缺少 await
    async function save() {
      const data = fetchData(); // 返回Promise
      saveToDB(data); // 保存的是Promise对象
    }
    
    // 正确
    async function save() {
      const data = await fetchData();
      saveToDB(data);
    }
    
  2. 循环中的并发控制

    // 危险:无限制并发
    async function massRequests() {
      const results = await Promise.all(urls.map(fetch));
    }
    
    // 安全:限制并发
    async function controlledRequests(urls, maxConcurrent = 5) {
      const results = [];
      for (let i = 0; i < urls.length; i += maxConcurrent) {
        const batch = urls.slice(i, i + maxConcurrent);
        const batchResults = await Promise.all(batch.map(fetch));
        results.push(...batchResults);
      }
      return results;
    }
    
  3. 未处理的拒绝

    // 危险:未处理的Promise拒绝
    async function dangerous() {
      const promise = fetchData(); // 缺少await和catch
    }
    
    // 安全:添加错误处理
    async function safe() {
      try {
        const data = await fetchData();
      } catch (err) {
        handleError(err);
      }
    }
    

八、转换工具与技巧

     实用工具函数

/**
 * 通用回调转Promise函数
 * @param {Function} fn - 回调风格函数
 * @returns {Function} - Promise风格函数
 */
function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, ...results) => {
        if (err) return reject(err);
        resolve(results.length > 1 ? results : results[0]);
      });
    });
  };
}

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

     浏览器环境转换

// XMLHttpRequest 转 Promise
function xhrPromise(url, method = 'GET') {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => reject(new Error('Network error'));
    xhr.send();
  });
}

// 使用
async function loadData() {
  try {
    const data = await xhrPromise('/api/data');
    console.log('加载数据:', data);
  } catch (err) {
    console.error('请求失败:', err);
  }
}

九、总结:转换策略流程图

graph TD
    A[回调函数] --> B{需要转换}
    B -->|是| C[手动封装Promise]
    B -->|是| D[使用util.promisify]
    C --> E[使用.then/.catch]
    D --> E
    E --> F[转换为async/await]
    F --> G[优化并行操作]
    G --> H[添加错误处理]
    H --> I[完成现代化改造]

     关键转换原则

  1. 识别回调模式:错误优先回调 vs 其他形式
  2. 选择合适的转换方法
    • 简单转换:手动封装 Promise
    • Node.js 环境:util.promisify
    • 复杂场景:自定义转换逻辑
  3. 错误处理
    • Promise:使用 .catch()
    • async/await:使用 try/catch
  4. 性能优化
    • 并行独立操作:Promise.all
    • 顺序依赖操作:顺序 await
  5. 渐进式重构
    • 先转换为 Promise
    • 再优化为 async/await
    • 最后进行性能优化

通过掌握回调转 Promise 和 async/await 的技术,你可以将遗留代码现代化,提升代码可读性和可维护性,同时享受现代 JavaScript 异步编程的强大功能。

















9️⃣ 参考 9

JavaScript 回调函数转 Promise 和 async/await 完全指南

在 JavaScript 中,将回调风格的异步代码转换为 Promise 或 async/await 可以显著提升代码可读性和可维护性。下面我将详细介绍各种转换方法和最佳实践。

一、回调函数基础模式

典型的 Node.js 风格回调(错误优先回调):

function callbackFunction(arg, callback) {
  // 异步操作
  setTimeout(() => {
    if (Math.random() > 0.5) {
      callback(null, 'Success!'); // 成功
    } else {
      callback(new Error('Failed'), null); // 失败
    }
  }, 1000);
}

二、回调转 Promise 的三种方法

     方法 1: 手动包装(最基础)

function promiseVersion(arg) {
  return new Promise((resolve, reject) => {
    callbackFunction(arg, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

// 使用
promiseVersion('input')
  .then(data => console.log(data))
  .catch(err => console.error(err));

     方法 2: 使用 Node.js 的 util.promisify

const util = require('util');

// 自动转换符合 Node.js 回调风格的函数
const promisifiedFunction = util.promisify(callbackFunction);

// 使用
promisifiedFunction('input')
  .then(data => console.log(data))
  .catch(err => console.error(err));

     方法 3: 高级转换(处理特殊回调模式)

function advancedPromisify(original) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      original(...args, (err, ...results) => {
        if (err) return reject(err);
        
        // 处理多个返回参数
        resolve(results.length > 1 ? results : results[0]);
      });
    });
  };
}

// 使用
const customPromisified = advancedPromisify(callbackFunction);

三、回调直接转 async/await

     基本转换

async function asyncVersion() {
  try {
    const result = await promiseVersion('input');
    console.log(result);
  } catch (error) {
    console.error('Error:', error);
  }
}

     处理多个异步操作

async function multipleOperations() {
  try {
    // 顺序执行
    const step1 = await operation1();
    const step2 = await operation2(step1);
    
    // 并行执行
    const [resultA, resultB] = await Promise.all([
      asyncOperationA(),
      asyncOperationB()
    ]);
    
    return { step2, resultA, resultB };
  } catch (error) {
    console.error('Process failed:', error);
    throw error; // 重新抛出错误
  }
}

四、特殊回调场景处理

     1. 事件监听器转 Promise

function eventToPromise(emitter, eventName) {
  return new Promise((resolve) => {
    emitter.once(eventName, resolve);
  });
}

// 使用示例
async function waitForEvent() {
  const result = await eventToPromise(myEmitter, 'data-ready');
  console.log('Event received:', result);
}

     2. 超时控制

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

// 使用
async function fetchWithTimeout() {
  try {
    const data = await withTimeout(fetchData(), 5000);
    console.log('Data received:', data);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

     3. 进度报告(Promise + 回调)

function processWithProgress() {
  return new Promise((resolve, reject) => {
    const processor = new DataProcessor();
    
    // 进度回调
    processor.onProgress = progress => {
      console.log(`Progress: ${progress}%`);
    };
    
    processor.process((err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

五、完整转换示例

     回调地狱 → Promise 链 → async/await

原始回调地狱:

function getUserData(userId, callback) {
  getUser(userId, (err, user) => {
    if (err) return callback(err);
    
    getOrders(user.id, (err, orders) => {
      if (err) return callback(err);
      
      getProducts(orders[0].id, (err, products) => {
        if (err) return callback(err);
        
        callback(null, { user, orders, products });
      });
    });
  });
}

转换为 Promise 链:

function getUserDataPromise(userId) {
  return getUserPromise(userId)
    .then(user => 
      Promise.all([user, getOrdersPromise(user.id)])
    .then(([user, orders]) => 
      Promise.all([user, orders, getProductsPromise(orders[0].id)])
    .then(([user, orders, products]) => {
      return { user, orders, products };
    });
}

转换为 async/await:

async function getUserDataAsync(userId) {
  try {
    const user = await getUserPromise(userId);
    const orders = await getOrdersPromise(user.id);
    const products = await getProductsPromise(orders[0].id);
    
    return { user, orders, products };
  } catch (error) {
    console.error('Failed to get user data:', error);
    throw error;
  }
}

六、最佳实践与注意事项

  1. 错误处理优先级

    // 好的做法:集中错误处理
    async function main() {
      try {
        await operation1();
        await operation2();
      } catch (error) {
        // 统一处理所有错误
      }
    }
    
    // 避免:每个操作单独 try/catch
    
  2. 并行优化

    // 慢:顺序执行
    async function slow() {
      const a = await fetchA();
      const b = await fetchB();
    }
    
    // 快:并行执行
    async function fast() {
      const [a, b] = await Promise.all([fetchA(), fetchB()]);
    }
    
  3. 循环中的异步处理

    // 正确:顺序处理
    async function processSequentially(items) {
      for (const item of items) {
        await processItem(item);
      }
    }
    
    // 正确:并行处理
    async function processParallel(items) {
      await Promise.all(items.map(item => processItem(item)));
    }
    
    // 错误:forEach 中的异步
    items.forEach(async (item) => {
      await processItem(item); // 不会等待
    });
    
  4. 资源清理

    async function withResource() {
      const resource = acquireResource();
      try {
        return await useResource(resource);
      } finally {
        releaseResource(resource); // 确保资源释放
      }
    }
    
  5. 混合使用策略

    // async/await 与 Promise 混合使用
    async function mixedUsage() {
      // 并行启动多个操作
      const userPromise = fetchUser();
      const postsPromise = fetchPosts();
      
      // 处理中间操作
      const profile = await buildProfile();
      
      // 等待并行结果
      const [user, posts] = await Promise.all([userPromise, postsPromise]);
      
      return { profile, user, posts };
    }
    

七、浏览器与 Node.js 环境差异

     浏览器环境

// 将 XMLHttpRequest 转换为 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('Request failed'));
    xhr.send();
  });
}

// 使用
async function loadData() {
  const data = await fetchUrl('/api/data');
  console.log(data);
}

     Node.js 环境

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

// 内置 promisify
const readFile = util.promisify(fs.readFile);

// 或者手动实现
const readFileManual = (path) => 
  new Promise((resolve, reject) => {
    fs.readFile(path, 'utf8', (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });

// 使用
async function processFile() {
  try {
    const content = await readFile('file.txt');
    console.log(content);
  } catch (err) {
    console.error('File read error:', err);
  }
}

总结转换策略

  1. 简单转换

    • 使用 new Promise 包装回调函数
    • Node.js 环境优先使用 util.promisify
  2. 高级场景

    • 事件监听器使用 once + Promise
    • 进度报告保留回调机制
    • 超时控制使用 Promise.race
  3. 代码组织

    • 将回调地狱转换为 Promise 链或 async/await
    • 区分顺序执行和并行执行
    • 统一错误处理
  4. 环境适配

    • 浏览器中转换传统 API
    • Node.js 利用内置工具

通过掌握这些转换技巧,你可以将任何回调风格的异步代码转换为更现代的 Promise 或 async/await 形式,显著提升代码质量和开发效率。

















🔟 参考 10

JavaScript 回调函数转 Promise 与 Async/Await 完全指南

在 JavaScript 中,将回调函数转换为 Promise 和 Async/Await 是现代化代码的关键步骤。下面我将详细展示转换方法,并提供实用示例。

回调函数基础模式

典型的 Node.js 风格回调(错误优先回调):

function asyncTask(arg1, arg2, callback) {
  // 模拟异步操作
  setTimeout(() => {
    const error = Math.random() > 0.7 ? new Error('出错了!') : null;
    const result = error ? null : `结果: ${arg1} - ${arg2}`;
    callback(error, result);
  }, 1000);
}

// 使用回调
asyncTask('参数1', '参数2', (err, result) => {
  if (err) {
    console.error('错误:', err.message);
    return;
  }
  console.log('回调结果:', result);
});

一、回调转 Promise

     1. 手动封装为 Promise

function promisifiedTask(arg1, arg2) {
  return new Promise((resolve, reject) => {
    asyncTask(arg1, arg2, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

// 使用 Promise
promisifiedTask('参数A', '参数B')
  .then(result => console.log('Promise 结果:', result))
  .catch(err => console.error('Promise 错误:', err.message));

     2. 通用转换函数

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) return reject(err);
        resolve(result);
      });
    });
  };
}

// 使用通用转换
const asyncTaskPromise = promisify(asyncTask);

asyncTaskPromise('通用参数', '测试')
  .then(console.log)
  .catch(console.error);

     3. Node.js 内置工具

Node.js 提供了 util.promisify 方法:

const util = require('util');

const asyncTaskPromise = util.promisify(asyncTask);

// 使用方式相同
asyncTaskPromise('node', 'util')
  .then(console.log)
  .catch(console.error);

二、回调转 Async/Await

     1. 基于 Promise 封装

// 首先创建 Promise 版本
const asyncTaskPromise = promisify(asyncTask);

// 创建 Async/Await 函数
async function runAsyncTask(param1, param2) {
  try {
    const result = await asyncTaskPromise(param1, param2);
    console.log('Async/Await 结果:', result);
    return result;
  } catch (err) {
    console.error('Async/Await 错误:', err.message);
    throw err; // 可选择重新抛出错误
  }
}

// 使用
runAsyncTask('异步', '等待');

     2. 直接转换嵌套回调

转换前(回调地狱):

function complexOperation(callback) {
  asyncTask('第一步', '数据', (err, step1Result) => {
    if (err) return callback(err);
    
    asyncTask('第二步', step1Result, (err, step2Result) => {
      if (err) return callback(err);
      
      asyncTask('第三步', step2Result, (err, finalResult) => {
        callback(err, finalResult);
      });
    });
  });
}

转换后(Async/Await):

async function complexOperationAsync() {
  try {
    const step1Result = await asyncTaskPromise('第一步', '数据');
    const step2Result = await asyncTaskPromise('第二步', step1Result);
    const finalResult = await asyncTaskPromise('第三步', step2Result);
    return finalResult;
  } catch (err) {
    console.error('复杂操作失败:', err);
    throw err;
  }
}

三、特殊回调模式转换

     1. 多参数回调转换

// 原始回调函数(多个成功参数)
function multiArgTask(callback) {
  setTimeout(() => {
    callback(null, '结果1', '结果2', '结果3');
  }, 500);
}

// 转换为 Promise
function promisifyMulti(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, ...results) => {
        if (err) return reject(err);
        // 将多个参数作为数组解析
        resolve(results.length > 1 ? results : results[0]);
      });
    });
  };
}

// 使用
const multiTaskPromise = promisifyMulti(multiArgTask);

multiTaskPromise()
  .then(([res1, res2, res3]) => {
    console.log(res1, res2, res3);
  });

     2. 事件型回调转换

// 原始事件驱动API
class EventEmitter {
  constructor() {
    this.listeners = [];
  }
  
  onData(callback) {
    this.listeners.push(callback);
  }
  
  emit(data) {
    this.listeners.forEach(cb => cb(data));
  }
}

// 转换为 Promise
function eventToPromise(emitter) {
  return new Promise((resolve) => {
    emitter.onData(data => {
      resolve(data);
    });
  });
}

// 使用
const emitter = new EventEmitter();

// 模拟数据发射
setTimeout(() => emitter.emit('数据包1'), 800);
setTimeout(() => emitter.emit('数据包2'), 1200);

// 获取第一个事件
eventToPromise(emitter)
  .then(data => console.log('第一个事件:', data));

四、高级转换技巧

     1. 回调超时控制

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

// 使用
async function fetchWithTimeout() {
  try {
    const result = await withTimeout(
      asyncTaskPromise('带超时', '控制'),
      800 // 800毫秒超时
    );
    console.log('成功:', result);
  } catch (err) {
    console.error('失败:', err.message);
  }
}

     2. 重试机制

async function retryAsyncOperation(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (err) {
    if (retries <= 0) throw err;
    console.log(`重试剩余 ${retries} 次...`);
    await new Promise(res => setTimeout(res, delay));
    return retryAsyncOperation(fn, retries - 1, delay);
  }
}

// 使用
retryAsyncOperation(
  () => asyncTaskPromise('可能失败的操作', '参数'),
  5, // 最大重试次数
  2000 // 重试间隔
).then(console.log).catch(console.error);

五、完整转换示例

     文件系统操作转换

const fs = require('fs');

// 回调版本
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) return console.error(err);
  console.log('文件内容:', data);
});

// Promise 版本
const readFilePromise = util.promisify(fs.readFile);

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

// Async/Await 版本
async function readAndProcessFile() {
  try {
    const content = await readFilePromise('file.txt', 'utf8');
    const processed = content.toUpperCase();
    await util.promisify(fs.writeFile)('output.txt', processed);
    console.log('文件处理完成');
  } catch (err) {
    console.error('文件操作失败:', err);
  }
}

六、转换模式对比

特性回调函数PromiseAsync/Await
可读性⭐⭐⭐⭐⭐
错误处理⭐⭐⭐⭐⭐
链式调用
嵌套处理❌ (回调地狱)
调试难度
并行处理困难Promise.all()await Promise.all()
流程控制手动链式同步风格

七、最佳实践

  1. 优先使用 Async/Await:提升代码可读性和可维护性
  2. 统一错误处理:使用 try/catch 块捕获异步错误
  3. 避免混合模式:不要在同一个函数中混用回调和 Promise
  4. 并行优化:使用 Promise.all() 处理独立异步操作
  5. 合理封装:对第三方库的回调 API 进行统一 Promise 化封装
  6. 添加超时:关键操作添加超时控制,防止永久阻塞

总结

JavaScript 异步编程的演进路径:

回调函数 → Promise → Async/Await

转换关键点:

  1. 使用 new Promise() 封装回调函数
  2. 在回调中调用 resolve()reject()
  3. 使用 util.promisify 简化 Node.js 风格回调转换
  4. 通过 async/await 以同步方式编写异步代码
  5. 使用 try/catch 处理异步错误

掌握这些转换技巧后,您可以将遗留的回调代码现代化,编写出更清晰、更易维护的 JavaScript 应用程序。