Promise使用

4 阅读4分钟

概念

Promise是JavaScript的ES6+规范中的处理异步操作的内置对象。

  • 状态:Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)

  • 不可逆性:状态一旦改变(从pending到fulfilled或rejected),就不会再变

  • 链式调用:通过.then().catch().finally()方法实现链式调用

优点

  • 链式调用:扁平化异步代码结构,避免回调地狱

  • 错误冒泡:错误可以统一在最后处理

  • 更好的可读性:代码逻辑更清晰

基本用法

1. 创建Promise

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

2. 使用Promise

promise
  .then(result => {
    console.log(result); // 成功时执行
  })
  .catch(error => {
    console.error(error.message); // 失败时执行
  })
  .finally(() => {
    console.log('无论成功失败都会执行');
  });

3. Promise的静态方法

Promise.resolve() / Promise.reject()

快速创建已确定状态的Promise:

// 立即成功的Promise
Promise.resolve('立即成功').then(console.log);

// 立即失败的Promise
Promise.reject(new Error('立即失败')).catch(console.error);

Promise.all()

等待所有Promise完成(全部成功或有一个失败):

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

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

Promise.allSettled()

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

const promises = [
  Promise.resolve('成功1'),
  Promise.reject('失败'),
  Promise.resolve('成功2')
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach(result => {
      console.log(result.status); // "fulfilled" 或 "rejected"
    });
  });

Promise.race()

返回最先完成的Promise(无论成功或失败):

const promise1 = new Promise(resolve => setTimeout(() => resolve('快'), 100));
const promise2 = new Promise(resolve => setTimeout(() => resolve('慢'), 200));

Promise.race([promise1, promise2])
  .then(result => console.log(result)); // "快"

Promise.any()

返回第一个成功的Promise(ES2021新增):

const promise1 = Promise.reject('失败1');
const promise2 = Promise.resolve('成功2');
const promise3 = Promise.reject('失败3');

Promise.any([promise1, promise2, promise3])
  .then(result => console.log(result)) // "成功2"
  .catch(errors => console.log(errors));

链式调用

Promise的核心优势在于链式调用,可以避免回调地狱:

function getUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ id, name: '张三' });
    }, 1000);
  });
}

function getPosts(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(['文章1', '文章2']);
    }, 1000);
  });
}

function getComments(postId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(['评论1', '评论2']);
    }, 1000);
  });
}

// 链式调用示例
getUser(1)
  .then(user => {
    console.log('用户:', user);
    return getPosts(user.id); // 返回新的Promise
  })
  .then(posts => {
    console.log('文章:', posts);
    return getComments(posts[0]);
  })
  .then(comments => {
    console.log('评论:', comments);
  })
  .catch(error => {
    console.error('出错:', error);
  });

关键点

  • 每个.then()可以返回一个值或新的Promise
  • 返回的值会被包装成Promise.resolve()
  • 错误会沿着链向下传递,直到被.catch()捕获

错误处理

1. 使用.catch()统一处理错误

someAsyncOperation()
  .then(result => {
    // 处理成功结果
  })
  .catch(error => {
    // 统一处理所有错误
    console.error('操作失败:', error);
  });

2. 在链式调用中处理错误

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .catch(error => {
    // 处理前面任何步骤的错误
    console.error(error);
  });

3. 避免在.then()中抛出错误

// 返回rejected Promise
promise.then(result => {
  if (!result) {
    // throw new Error('结果为空'); // 不要在then中抛出错误
    return Promise.reject(new Error('结果为空'));
  }
  return result;
});

异步任务类型

Promise的回调属于微任务,会在当前事件循环的微任务阶段执行,优先级高于宏任务(setTimeout等):

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// 输出顺序:1 → 4 → 3 → 2

未处理的reject

未处理的reject可能导致内存泄漏或程序崩溃:

// 添加错误处理,即.catch
someAsyncOperation()
  .then(result => console.log(result))
  .catch(error => console.error(error));

async/await

async/await是ES2017引入的语法糖,基于Promise,让异步代码看起来像同步代码:

  • 在async函数中使用try-catch处理错误
  • await只能用在async函数中
  • async函数总是返回Promise
// 使用Promise
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => data);
}

// 使用async/await
async function fetchData() {
  const response = await fetch('/api/data');
  const data = await response.json();
  return data;
}

应用

1、并发请求优化

// 串行请求(慢)
async function serialRequests() {
  const user = await getUser(1);
  const posts = await getPosts(user.id);
  const comments = await getComments(posts[0]);
  return comments;
}

// 并发请求(快)
async function parallelRequests() {
  const [user, posts, comments] = await Promise.all([
    getUser(1),
    getPosts(1),
    getComments(1)
  ]);
  return { user, posts, comments };
}

2、超时控制

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

// 使用
withTimeout(fetch('/api/data'), 5000)
  .then(response => response.json())
  .catch(error => {
    if (error.message === '请求超时') {
      console.log('超时了');
    } else {
      console.error('其他错误:', error);
    }
  });

3、重试机制

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

// 使用
retry(() => fetch('/api/data'), 3, 1000)
  .then(response => response.json())
  .catch(error => console.error('重试3次后失败:', error));