Promise是JavaScript中处理异步操作的现代解决方案,它解决了传统回调函数的诸多问题。

Promise的基本概念

什么是Promise?

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

  • Pending(待定):初始状态,既没有被兑现,也没有被拒绝
  • Fulfilled(已兑现):操作成功完成
  • Rejected(已拒绝):操作失败
// Promise的基本语法
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 操作成功 */) {
    resolve(value); // 成功时调用
  } else {
    reject(error);  // 失败时调用
  }
});

Promise解决的问题

1. 回调地狱(Callback Hell)

// 传统回调方式 - 回调地狱
function getUserData(userId, callback) {
  setTimeout(() => {
    console.log('获取用户数据');
    callback(null, { id: userId, name: 'John' });
  }, 1000);
}
 
function getOrderData(userId, callback) {
  setTimeout(() => {
    console.log('获取订单数据');
    callback(null, [{ id: 1, product: 'iPhone' }]);
  }, 1000);
}
 
function getProductDetails(productId, callback) {
  setTimeout(() => {
    console.log('获取产品详情');
    callback(null, { id: productId, price: 999 });
  }, 1000);
}
 
// 嵌套回调 - 难以维护
getUserData(123, (err, user) => {
  if (err) {
    console.error(err);
    return;
  }
  
  getOrderData(user.id, (err, orders) => {
    if (err) {
      console.error(err);
      return;
    }
    
    getProductDetails(orders[0].id, (err, product) => {
      if (err) {
        console.error(err);
        return;
      }
      
      console.log('最终结果:', { user, orders, product });
    });
  });
});
// 使用Promise - 链式调用,更清晰
function getUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('获取用户数据');
      resolve({ id: userId, name: 'John' });
    }, 1000);
  });
}
 
function getOrderData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('获取订单数据');
      resolve([{ id: 1, product: 'iPhone' }]);
    }, 1000);
  });
}
 
function getProductDetails(productId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('获取产品详情');
      resolve({ id: productId, price: 999 });
    }, 1000);
  });
}
 
// 链式调用 - 扁平化结构
getUserData(123)
  .then(user => {
    console.log('用户:', user);
    return getOrderData(user.id);
  })
  .then(orders => {
    console.log('订单:', orders);
    return getProductDetails(orders[0].id);
  })
  .then(product => {
    console.log('产品:', product);
  })
  .catch(error => {
    console.error('错误:', error);
  });

Promise的基本用法

1. 创建Promise

// 基本创建方式
const simplePromise = new Promise((resolve, reject) => {
  const success = Math.random() > 0.5;
  
  setTimeout(() => {
    if (success) {
      resolve('操作成功!');
    } else {
      reject(new Error('操作失败!'));
    }
  }, 1000);
});
 
// 使用Promise
simplePromise
  .then(result => {
    console.log('成功:', result);
  })
  .catch(error => {
    console.log('失败:', error.message);
  });

2. Promise.resolve() 和 Promise.reject()

// 创建已解决的Promise
const resolvedPromise = Promise.resolve('立即成功');
const rejectedPromise = Promise.reject(new Error('立即失败'));
 
// 包装非Promise值
Promise.resolve(42)
  .then(value => console.log(value)); // 42
 
// 包装thenable对象
const thenable = {
  then(resolve, reject) {
    resolve('thenable值');
  }
};
 
Promise.resolve(thenable)
  .then(value => console.log(value)); // 'thenable值'

3. 链式调用

Promise.resolve(1)
  .then(value => {
    console.log('第一步:', value); // 1
    return value * 2;
  })
  .then(value => {
    console.log('第二步:', value); // 2
    return value * 3;
  })
  .then(value => {
    console.log('第三步:', value); // 6
    return Promise.resolve(value * 4);
  })
  .then(value => {
    console.log('第四步:', value); // 24
  })
  .catch(error => {
    console.error('错误:', error);
  });

Promise的静态方法

1. Promise.all() - 并行执行,全部成功

const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve('foo'), 1000));
const promise3 = Promise.resolve(42);
 
Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [3, 'foo', 42]
  })
  .catch(error => {
    console.error('有一个失败了:', error);
  });
 
// 实际应用:并行获取多个API数据
async function fetchAllData() {
  try {
    const [users, products, orders] = await Promise.all([
      fetch('/api/users').then(res => res.json()),
      fetch('/api/products').then(res => res.json()),
      fetch('/api/orders').then(res => res.json())
    ]);
    
    return { users, products, orders };
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

2. Promise.allSettled() - 等待所有Promise完成

const promises = [
  Promise.resolve('成功1'),
  Promise.reject(new Error('失败1')),
  Promise.resolve('成功2'),
  Promise.reject(new Error('失败2'))
];
 
Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Promise ${index} 成功:`, result.value);
      } else {
        console.log(`Promise ${index} 失败:`, result.reason.message);
      }
    });
  });
 
// 输出:
// Promise 0 成功: 成功1
// Promise 1 失败: 失败1
// Promise 2 成功: 成功2
// Promise 3 失败: 失败2

3. Promise.race() - 竞态,返回最先完成的

const promise1 = new Promise(resolve => setTimeout(() => resolve('慢的'), 2000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('快的'), 1000));
 
Promise.race([promise1, promise2])
  .then(value => {
    console.log(value); // '快的'
  });
 
// 实际应用:请求超时控制
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('请求超时')), timeout)
  );
  
  return Promise.race([fetchPromise, timeoutPromise]);
}
 
fetchWithTimeout('/api/data', 3000)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error.message));

4. Promise.any() - 返回第一个成功的

const promises = [
  Promise.reject(new Error('错误1')),
  Promise.reject(new Error('错误2')),
  Promise.resolve('成功'),
  Promise.resolve('另一个成功')
];
 
Promise.any(promises)
  .then(value => {
    console.log('第一个成功的:', value); // '成功'
  })
  .catch(error => {
    console.log('所有都失败了:', error);
  });

错误处理

1. catch方法

Promise.resolve()
  .then(() => {
    throw new Error('出错了!');
  })
  .then(() => {
    console.log('这里不会执行');
  })
  .catch(error => {
    console.error('捕获错误:', error.message);
  });

2. finally方法

function fetchData() {
  const loadingElement = document.querySelector('#loading');
  loadingElement.style.display = 'block';
  
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      console.log('数据:', data);
      return data;
    })
    .catch(error => {
      console.error('错误:', error);
      throw error;
    })
    .finally(() => {
      // 无论成功失败都会执行
      loadingElement.style.display = 'none';
    });
}

3. 错误传播

Promise.resolve()
  .then(() => {
    throw new Error('第一个错误');
  })
  .then(() => {
    console.log('不会执行');
  })
  .catch(error => {
    console.log('捕获:', error.message);
    throw new Error('第二个错误');
  })
  .then(() => {
    console.log('也不会执行');
  })
  .catch(error => {
    console.log('最终捕获:', error.message);
  });

实际应用场景

1. 网络请求封装

class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }
  
  request(url, options = {}) {
    return fetch(this.baseURL + url, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    })
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      return response.json();
    });
  }
  
  get(url) {
    return this.request(url);
  }
  
  post(url, data) {
    return this.request(url, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
}
 
// 使用
const api = new ApiClient('https://api.example.com');
 
api.get('/users/123')
  .then(user => console.log(user))
  .catch(error => console.error('获取用户失败:', error));

2. 图片预加载

function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`图片加载失败: ${src}`));
    
    img.src = src;
  });
}
 
function preloadImages(urls) {
  const promises = urls.map(url => loadImage(url));
  
  return Promise.allSettled(promises)
    .then(results => {
      const loaded = results
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value);
      
      const failed = results
        .filter(result => result.status === 'rejected')
        .map(result => result.reason);
      
      return { loaded, failed };
    });
}
 
// 使用
const imageUrls = [
  'image1.jpg',
  'image2.jpg',
  'image3.jpg'
];
 
preloadImages(imageUrls)
  .then(({ loaded, failed }) => {
    console.log(`成功加载 ${loaded.length} 张图片`);
    console.log(`失败 ${failed.length} 张图片`);
  });

3. 缓存机制

class CacheManager {
  constructor() {
    this.cache = new Map();
    this.pendingPromises = new Map();
  }
  
  async get(key, fetchFunction) {
    // 如果缓存中有数据,直接返回
    if (this.cache.has(key)) {
      return Promise.resolve(this.cache.get(key));
    }
    
    // 如果正在请求中,返回相同的Promise
    if (this.pendingPromises.has(key)) {
      return this.pendingPromises.get(key);
    }
    
    // 创建新的请求Promise
    const promise = fetchFunction()
      .then(data => {
        this.cache.set(key, data);
        this.pendingPromises.delete(key);
        return data;
      })
      .catch(error => {
        this.pendingPromises.delete(key);
        throw error;
      });
    
    this.pendingPromises.set(key, promise);
    return promise;
  }
  
  clear() {
    this.cache.clear();
    this.pendingPromises.clear();
  }
}
 
// 使用
const cache = new CacheManager();
 
function fetchUser(id) {
  return cache.get(`user-${id}`, () =>
    fetch(`/api/users/${id}`).then(res => res.json())
  );
}
 
// 多次调用只会发送一次请求
Promise.all([
  fetchUser(123),
  fetchUser(123),
  fetchUser(123)
]).then(users => {
  console.log('所有用户数据:', users);
});

Promise与async/await

传统Promise写法

function processData() {
  return fetchUser(123)
    .then(user => {
      return fetchOrders(user.id);
    })
    .then(orders => {
      return Promise.all(
        orders.map(order => fetchOrderDetails(order.id))
      );
    })
    .then(orderDetails => {
      return {
        user,
        orders: orderDetails
      };
    })
    .catch(error => {
      console.error('处理数据失败:', error);
      throw error;
    });
}

async/await写法

async function processData() {
  try {
    const user = await fetchUser(123);
    const orders = await fetchOrders(user.id);
    const orderDetails = await Promise.all(
      orders.map(order => fetchOrderDetails(order.id))
    );
    
    return {
      user,
      orders: orderDetails
    };
  } catch (error) {
    console.error('处理数据失败:', error);
    throw error;
  }
}

性能考虑

1. 避免不必要的Promise包装

// 不好 - 不必要的Promise包装
function badExample(value) {
  return new Promise(resolve => {
    resolve(value * 2);
  });
}
 
// 好 - 直接返回值或使用Promise.resolve
function goodExample(value) {
  return Promise.resolve(value * 2);
}
 
// 更好 - 如果是同步操作,直接返回值
function bestExample(value) {
  return value * 2;
}

2. 并行 vs 串行

// 串行执行 - 慢
async function serialExample() {
  const user = await fetchUser(123);    // 1秒
  const orders = await fetchOrders(456); // 1秒
  const products = await fetchProducts(789); // 1秒
  // 总时间: 3秒
  return { user, orders, products };
}
 
// 并行执行 - 快
async function parallelExample() {
  const [user, orders, products] = await Promise.all([
    fetchUser(123),      // 1秒
    fetchOrders(456),    // 1秒  
    fetchProducts(789)   // 1秒
  ]);
  // 总时间: 1秒
  return { user, orders, products };
}

总结

Promise的核心价值:

  1. 解决回调地狱 - 提供链式调用语法
  2. 统一异步处理 - 标准化异步操作接口
  3. 更好的错误处理 - 集中化错误处理机制
  4. 组合能力 - 提供并行、竞态等组合方法
  5. 与现代语法兼容 - 完美支持async/await

Promise是现代JavaScript异步编程的基础,理解其原理和用法对于编写高质量的异步代码至关重要。