您现在的位置是:网站首页 > Promise原理与使用文章详情

Promise原理与使用

Promise的基本概念

Promise是JavaScript中处理异步操作的一种机制,它代表一个尚未完成但预期将来会完成的操作。Promise对象有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      resolve(randomNumber);
    } else {
      reject(new Error('数字太小'));
    }
  }, 1000);
});

Promise的核心原理

Promise的实现基于观察者模式,内部维护了两个队列:

  1. 成功回调队列(onFulfilledCallbacks)
  2. 失败回调队列(onRejectedCallbacks)

当Promise状态改变时,会依次执行对应队列中的回调函数。这种设计使得异步操作可以按照顺序执行,避免了回调地狱。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(cb => cb(value));
      }
    };
    
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = reason;
        this.onRejectedCallbacks.forEach(cb => cb(reason));
      }
    };
    
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

Promise的常用方法

then方法

then方法是Promise的核心,它接收两个参数:

  • onFulfilled:成功回调
  • onRejected:失败回调
promise.then(
  value => {
    console.log('成功:', value);
  },
  error => {
    console.error('失败:', error);
  }
);

catch方法

catch方法是then(null, onRejected)的语法糖,专门用于处理错误。

promise
  .then(value => {
    console.log('成功:', value);
  })
  .catch(error => {
    console.error('捕获错误:', error);
  });

finally方法

finally方法无论Promise最终状态如何都会执行,适合用于清理工作。

promise
  .then(value => {
    console.log('成功:', value);
  })
  .catch(error => {
    console.error('错误:', error);
  })
  .finally(() => {
    console.log('操作完成');
  });

Promise的静态方法

Promise.resolve

创建一个立即resolve的Promise。

const resolvedPromise = Promise.resolve('立即解决');
resolvedPromise.then(value => console.log(value)); // 输出: 立即解决

Promise.reject

创建一个立即reject的Promise。

const rejectedPromise = Promise.reject(new Error('立即拒绝'));
rejectedPromise.catch(error => console.error(error)); // 输出: Error: 立即拒绝

Promise.all

等待所有Promise完成,或者第一个Promise失败。

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1000));

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // 输出: [1, 2, 3]
  })
  .catch(error => {
    console.error(error);
  });

Promise.race

返回第一个settled的Promise(无论成功或失败)。

const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 500));
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 200));

Promise.race([promise1, promise2])
  .then(value => {
    console.log(value); // 输出: 2
  });

Promise的高级用法

Promise链式调用

Promise的then方法返回一个新的Promise,使得链式调用成为可能。

function fetchUser(userId) {
  return Promise.resolve({ id: userId, name: '张三' });
}

function fetchPosts(userId) {
  return Promise.resolve([
    { id: 1, title: '文章1' },
    { id: 2, title: '文章2' }
  ]);
}

fetchUser(123)
  .then(user => {
    console.log('用户:', user);
    return fetchPosts(user.id);
  })
  .then(posts => {
    console.log('文章:', posts);
  })
  .catch(error => {
    console.error('错误:', error);
  });

Promise错误处理

Promise的错误会沿着链一直传递,直到被catch捕获。

Promise.resolve()
  .then(() => {
    throw new Error('第一个错误');
  })
  .then(() => {
    console.log('这里不会执行');
  })
  .catch(error => {
    console.error('捕获错误:', error); // 输出: Error: 第一个错误
    throw new Error('第二个错误');
  })
  .catch(error => {
    console.error('捕获第二个错误:', error); // 输出: Error: 第二个错误
  });

Promise与async/await

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

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

getUserData(123)
  .then(data => console.log('数据:', data))
  .catch(error => console.error('最终错误:', error));

Promise的实际应用场景

文件读取

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

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

readFiles();

网络请求

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(`请求失败: ${xhr.status}`));
      }
    };
    xhr.onerror = () => reject(new Error('网络错误'));
    xhr.send();
  });
}

fetchData('https://api.example.com/data')
  .then(data => console.log(data))
  .catch(error => console.error(error));

定时器封装

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function timedOperation() {
  console.log('开始');
  await delay(1000);
  console.log('1秒后');
  await delay(2000);
  console.log('再过2秒后');
}

timedOperation();

Promise的注意事项

避免Promise嵌套

虽然Promise解决了回调地狱,但不正确的使用仍会导致Promise嵌套。

// 不推荐
getUser(userId).then(user => {
  getPosts(user.id).then(posts => {
    getComments(posts[0].id).then(comments => {
      console.log(comments);
    });
  });
});

// 推荐
getUser(userId)
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error(error));

不要忘记返回Promise

在then回调中,如果忘记返回Promise,链式调用会中断。

// 错误示例
getUser(userId)
  .then(user => {
    getPosts(user.id); // 忘记return
  })
  .then(posts => {
    console.log(posts); // undefined
  });

// 正确示例
getUser(userId)
  .then(user => {
    return getPosts(user.id);
  })
  .then(posts => {
    console.log(posts);
  });

处理未捕获的Promise错误

未捕获的Promise错误可能会导致难以调试的问题。

// 全局捕获未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的拒绝:', reason);
});

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

Promise的性能优化

并行执行

使用Promise.all可以并行执行多个独立操作,提高效率。

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

限制并发数

当需要处理大量Promise时,控制并发数可以避免资源耗尽。

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

取消Promise

原生Promise不支持取消,但可以通过包装实现。

function cancellablePromise(executor) {
  let rejectFn;
  const promise = new Promise((resolve, reject) => {
    rejectFn = reject;
    executor(resolve, reject);
  });
  
  promise.cancel = (reason) => {
    rejectFn(new Error(reason || 'Promise被取消'));
  };
  
  return promise;
}

const p = cancellablePromise((resolve, reject) => {
  setTimeout(() => resolve('完成'), 2000);
});

p.then(console.log).catch(console.error);

// 1秒后取消
setTimeout(() => p.cancel('用户取消'), 1000);

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步