Node.js Promise 对象

核心知识点

Promise 概念

Promise 是一种用于处理异步操作的对象,它代表一个可能在未来完成或失败的操作。在 Node.js 中,Promise 提供了一种更优雅的方式来处理异步代码,避免回调地狱。

Promise 状态

Promise 有三种状态:

  1. Pending(待定):初始状态,操作尚未完成
  2. Fulfilled(已完成):操作成功完成
  3. Rejected(已拒绝):操作失败

Promise 基础语法

创建和使用 Promise 的基本语法:

const promise = new Promise((resolve, reject) => {
  // 执行异步操作
  if (操作成功) {
    resolve(result); // 成功时调用 resolve
  } else {
    reject(error); // 失败时调用 reject
  }
});

// 使用 Promise
promise
  .then(result => {
    // 处理成功结果
  })
  .catch(error => {
    // 处理错误
  });

Promise 链式调用

Promise 可以通过链式调用处理多个异步操作:

promise
  .then(result1 => {
    // 处理第一个结果
    return processResult(result1); // 返回另一个 Promise
  })
  .then(result2 => {
    // 处理第二个结果
    return anotherOperation(result2); // 返回另一个 Promise
  })
  .then(result3 => {
    // 处理最终结果
  })
  .catch(error => {
    // 处理任何环节的错误
  });

Promise 静态方法

Promise 提供了几个有用的静态方法:

  1. **Promise.all()**:等待所有 Promise 完成
  2. **Promise.race()**:等待第一个完成的 Promise
  3. **Promise.resolve()**:创建一个已完成的 Promise
  4. **Promise.reject()**:创建一个已拒绝的 Promise

实用案例

案例一:基本 Promise 使用

const fs = require('fs');

// 封装文件读取为 Promise
function readFilePromise(filename, encoding) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, encoding, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

// 封装文件写入为 Promise
function writeFilePromise(filename, content) {
  return new Promise((resolve, reject) => {
    fs.writeFile(filename, content, (err) => {
      if (err) {
        reject(err);
      } else {
        resolve('File written successfully');
      }
    });
  });
}

// 使用 Promise 处理文件操作
readFilePromise('example.txt', 'utf8')
  .then(data => {
    console.log('读取的文件内容:', data);
    return writeFilePromise('output.txt', data.toUpperCase());
  })
  .then(message => {
    console.log(message);
    return readFilePromise('output.txt', 'utf8');
  })
  .then(newData => {
    console.log('写入后读取的内容:', newData);
  })
  .catch(err => {
    console.error('发生错误:', err);
  });

案例二:Promise 链式调用

const http = require('http');

// 封装 HTTP 请求为 Promise
function httpGetPromise(url) {
  return new Promise((resolve, reject) => {
    http.get(url, (response) => {
      let data = '';
      
      response.on('data', (chunk) => {
        data += chunk;
      });
      
      response.on('end', () => {
        resolve(data);
      });
      
      response.on('error', (error) => {
        reject(error);
      });
    }).on('error', (error) => {
      reject(error);
    });
  });
}

// 链式调用 Promise
httpGetPromise('http://example.com')
  .then(data => {
    console.log('获取到数据,长度:', data.length);
    // 处理数据
    const processedData = data.substring(0, 1000);
    console.log('处理后的数据长度:', processedData.length);
    return processedData;
  })
  .then(processedData => {
    // 进一步处理
    console.log('数据前50个字符:', processedData.substring(0, 50) + '...');
    return '处理完成';
  })
  .then(result => {
    console.log('最终结果:', result);
  })
  .catch(err => {
    console.error('发生错误:', err);
  });

案例三:Promise 静态方法

const fs = require('fs');

// 封装文件读取为 Promise
function readFilePromise(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

// 使用 Promise.all() 并行读取多个文件
Promise.all([
  readFilePromise('file1.txt'),
  readFilePromise('file2.txt'),
  readFilePromise('file3.txt')
])
  .then(results => {
    console.log('所有文件读取完成');
    console.log('文件1内容:', results[0]);
    console.log('文件2内容:', results[1]);
    console.log('文件3内容:', results[2]);
    
    // 合并内容
    const combinedContent = results.join('\n');
    console.log('合并后的内容:', combinedContent);
  })
  .catch(err => {
    console.error('读取文件时发生错误:', err);
  });

// 使用 Promise.race() 竞争读取
Promise.race([
  readFilePromise('file1.txt'),
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('超时')), 1000);
  })
])
  .then(data => {
    console.log('最快完成的操作结果:', data);
  })
  .catch(err => {
    console.error('竞争失败:', err);
  });

案例四:使用 Promise 重构回调代码

const fs = require('fs');

// 回调版本
function processFilesCallback() {
  fs.readFile('file1.txt', 'utf8', (err1, data1) => {
    if (err1) {
      console.error('读取文件1失败:', err1);
      return;
    }
    
    fs.readFile('file2.txt', 'utf8', (err2, data2) => {
      if (err2) {
        console.error('读取文件2失败:', err2);
        return;
      }
      
      const combined = data1 + data2;
      
      fs.writeFile('combined.txt', combined, (err3) => {
        if (err3) {
          console.error('写入文件失败:', err3);
          return;
        }
        console.log('文件处理完成');
      });
    });
  });
}

// Promise 版本
function readFile(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

function writeFile(filename, content) {
  return new Promise((resolve, reject) => {
    fs.writeFile(filename, content, (err) => {
      if (err) reject(err);
      else resolve('写入成功');
    });
  });
}

function processFilesPromise() {
  readFile('file1.txt')
    .then(data1 => {
      return Promise.all([Promise.resolve(data1), readFile('file2.txt')]);
    })
    .then(([data1, data2]) => {
      const combined = data1 + data2;
      return writeFile('combined.txt', combined);
    })
    .then(message => {
      console.log(message);
      console.log('文件处理完成');
    })
    .catch(err => {
      console.error('处理文件时发生错误:', err);
    });
}

// 调用两种版本
console.log('使用回调处理文件:');
processFilesCallback();

console.log('\n使用 Promise 处理文件:');
processFilesPromise();

学习目标

  1. 理解 Promise 概念:掌握 Promise 的基本原理和状态变化
  2. 掌握 Promise 语法:学会创建和使用 Promise
  3. 熟练链式调用:能够使用 Promise 链式处理多个异步操作
  4. 掌握错误处理:学会在 Promise 中处理错误
  5. 使用静态方法:掌握 Promise.all() 等静态方法的使用
  6. 重构回调代码:能够将回调风格的代码重构为 Promise 风格

代码优化建议

1. 合理使用 Promise 链式调用

不好的做法

// 嵌套的 Promise
promise1.then(result1 => {
  promise2.then(result2 => {
    promise3.then(result3 => {
      console.log(result1, result2, result3);
    });
  });
});

好的做法

// 链式调用
promise1
  .then(result1 => {
    return promise2;
  })
  .then(result2 => {
    return promise3;
  })
  .then(result3 => {
    console.log(result3);
  });

2. 正确处理错误

不好的做法

promise
  .then(result => {
    // 处理结果
    if (someErrorCondition) {
      throw new Error('发生错误');
    }
    return nextOperation(result);
  })
  // 缺少 catch

好的做法

promise
  .then(result => {
    // 处理结果
    if (someErrorCondition) {
      throw new Error('发生错误');
    }
    return nextOperation(result);
  })
  .catch(error => {
    console.error('处理错误:', error);
    // 可以在这里进行错误恢复
  });

3. 使用 Promise.all() 并行处理

不好的做法

// 串行处理,效率低
asyncOperation1()
  .then(result1 => {
    return asyncOperation2();
  })
  .then(result2 => {
    return asyncOperation3();
  })
  .then(result3 => {
    console.log(result1, result2, result3);
  });

好的做法

// 并行处理,效率高
Promise.all([
  asyncOperation1(),
  asyncOperation2(),
  asyncOperation3()
])
  .then(([result1, result2, result3]) => {
    console.log(result1, result2, result3);
  });

常见问题与解决方案

问题1:Promise 未处理的拒绝

原因

  • Promise 被拒绝但没有对应的 catch 处理

解决方案

  • 始终为 Promise 添加 catch 处理
  • 可以在全局添加 unhandledRejection 事件监听器
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason);
});

问题2:Promise 链式调用中的错误传播

原因

  • 链式调用中的某个 Promise 被拒绝

解决方案

  • 在链式调用的末尾添加 catch 处理
  • 可以在中间添加 catch 处理特定错误并继续链式调用

问题3:Promise 内存泄漏

原因

  • 创建了 Promise 但没有正确处理(既没有 then 也没有 catch)

解决方案

  • 确保每个 Promise 都有对应的处理
  • 使用 Promise.race() 设置超时

总结

Promise 是 Node.js 中处理异步操作的重要工具,通过本教程的学习,你应该能够:

  1. 理解 Promise 的基本概念和状态变化
  2. 掌握 Promise 的创建和使用方法
  3. 熟练使用 Promise 链式调用处理多个异步操作
  4. 正确处理 Promise 中的错误
  5. 掌握 Promise 静态方法的使用
  6. 将回调风格的代码重构为 Promise 风格

Promise 为异步编程提供了更清晰、更可维护的方式,是学习更高级的 async/await 语法的基础。在后续章节中,我们将学习 async/await,它是基于 Promise 的更现代的异步编程解决方案。

« 上一篇 Node.js 回调函数 下一篇 » Node.js async/await 语法