阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Promise原理与使用

Promise原理与使用

作者:陈川 阅读数:63659人阅读 分类: Node.js

Promise是JavaScript中处理异步操作的重要机制,尤其在Node.js环境下,它简化了回调地狱问题,提供了更清晰的代码结构。理解Promise的原理和用法对于编写高效、可维护的异步代码至关重要。

Promise的基本概念

Promise是一个表示异步操作最终完成或失败的对象。它有三种状态:

  • pending:初始状态,既不是成功,也不是失败
  • fulfilled:操作成功完成
  • rejected:操作失败

Promise的状态一旦改变就不可逆,从pending变为fulfilled或rejected后,状态就固定了。

const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

Promise的创建与使用

创建Promise需要传入一个执行器函数,该函数接收两个参数:resolve和reject。当异步操作成功时调用resolve,失败时调用reject。

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

Promise链式调用

Promise的then方法返回一个新的Promise,这使得链式调用成为可能。每个then处理前一个Promise的结果。

fetchData('https://api.example.com/data')
  .then(response => {
    console.log(response);
    return processData(response);
  })
  .then(processedData => {
    console.log(processedData);
    return saveData(processedData);
  })
  .catch(error => {
    console.error('处理过程中出错:', error);
  });

错误处理

Promise提供了catch方法来处理链中任何地方发生的错误。错误会沿着链向下传递,直到被捕获。

someAsyncOperation()
  .then(result => {
    return anotherAsyncOperation(result);
  })
  .then(newResult => {
    return finalAsyncOperation(newResult);
  })
  .catch(error => {
    // 处理前面任何步骤中的错误
    console.error(error);
  });

Promise的静态方法

Promise类提供了一些有用的静态方法:

Promise.all

等待所有Promise完成,或第一个Promise被拒绝。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values); // [3, 42, "foo"]
});

Promise.race

返回第一个完成或拒绝的Promise的结果。

const promise1 = new Promise((resolve) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value); // "two"
});

Promise.allSettled

等待所有Promise完成(无论成功或失败)。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((_, reject) => 
  setTimeout(reject, 100, 'foo')
);

Promise.allSettled([promise1, promise2]).then((results) => 
  results.forEach((result) => console.log(result.status))
);
// "fulfilled"
// "rejected"

Promise在Node.js中的实际应用

在Node.js中,Promise常用于文件操作、数据库查询等异步场景。许多Node.js核心模块现在都支持Promise风格的API。

文件系统操作

const fs = require('fs').promises;

async function readFiles() {
  try {
    const data1 = await fs.readFile('file1.txt', 'utf8');
    const data2 = await fs.readFile('file2.txt', 'utf8');
    console.log(data1, data2);
  } catch (err) {
    console.error('读取文件出错:', err);
  }
}

readFiles();

数据库查询

const { MongoClient } = require('mongodb');

async function queryDatabase() {
  const client = new MongoClient(uri);
  
  try {
    await client.connect();
    const database = client.db("sample_mflix");
    const movies = database.collection("movies");
    const query = { title: "The Room" };
    const movie = await movies.findOne(query);
    console.log(movie);
  } finally {
    await client.close();
  }
}

queryDatabase();

Promise的进阶用法

Promise化回调函数

将传统的回调函数转换为返回Promise的函数:

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

const readFile = promisify(fs.readFile);
readFile('example.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

自定义Promise实现

理解Promise的工作原理可以通过实现一个简化版Promise:

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 实现Promise解决过程
}

Promise的性能考虑

虽然Promise提供了更好的代码组织方式,但也需要注意一些性能问题:

  1. 内存使用:每个Promise都会创建新的微任务,大量Promise可能增加内存压力
  2. 错误堆栈:Promise链中的错误堆栈可能不如同步代码清晰
  3. 调试难度:异步代码的调试通常比同步代码更复杂
// 不推荐的写法 - 创建了不必要的Promise
function badPractice() {
  return new Promise(resolve => {
    someAsyncOperation().then(result => {
      resolve(result);
    });
  });
}

// 推荐的写法 - 直接返回Promise
function goodPractice() {
  return someAsyncOperation();
}

Promise与async/await的关系

async/await是建立在Promise之上的语法糖,使异步代码看起来更像同步代码。

async function getUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    return { user, posts, comments };
  } catch (error) {
    console.error('获取用户数据失败:', error);
    throw error;
  }
}

// 等同于
function getUserData(userId) {
  return fetchUser(userId)
    .then(user => fetchPosts(user.id))
    .then(posts => fetchComments(posts[0].id))
    .then(comments => ({ user, posts, comments }))
    .catch(error => {
      console.error('获取用户数据失败:', error);
      throw error;
    });
}

Promise的常见陷阱

  1. 忘记返回Promise:在then回调中忘记return会导致链断裂
  2. 错误未被捕获:未处理的Promise拒绝可能导致难以调试的问题
  3. 过度嵌套:虽然Promise解决了回调地狱,但不当使用仍可能导致嵌套问题
// 错误示例 - 忘记返回Promise
somePromise()
  .then(result => {
    anotherPromise(result); // 忘记return
  })
  .then(finalResult => {
    // finalResult将是undefined
  });

// 正确写法
somePromise()
  .then(result => {
    return anotherPromise(result);
  })
  .then(finalResult => {
    // 正确处理finalResult
  });

Promise在Node.js生态系统中的应用

许多流行的Node.js库都采用了Promise作为主要的异步处理方式:

  1. Express中间件:现代Express中间件通常支持Promise
  2. ORM/ODM:如Sequelize、Mongoose等数据库工具广泛使用Promise
  3. HTTP客户端:Axios、node-fetch等库返回Promise
// Express中使用Promise
app.get('/api/users', async (req, res, next) => {
  try {
    const users = await UserModel.find();
    res.json(users);
  } catch (err) {
    next(err);
  }
});

// Mongoose中使用Promise
UserModel.findOne({ name: 'John' })
  .then(user => {
    if (!user) throw new Error('用户未找到');
    return user.updateOne({ age: 30 });
  })
  .then(() => console.log('更新成功'))
  .catch(err => console.error(err));

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:回调函数模式

下一篇:async/await语法糖

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌