返回首页

你是怎么理解ES6中Promise的?使用场景?

问题解析

Promise 是 ES6 引入的异步编程解决方案,用于解决回调地狱问题。这个问题考察对 Promise 核心概念、状态流转、方法使用以及实际应用场景的理解。

核心概念

1. Promise 是什么

  • 异步编程的一种解决方案,比传统的回调函数和事件更合理
  • 代表一个异步操作的最终完成(或失败)及其结果值

2. 三种状态

  • pending(进行中):初始状态
  • fulfilled(已成功):操作成功完成
  • rejected(已失败):操作失败
  • 状态一旦改变就不可再变(pending -> fulfilledpending -> rejected

3. 基本用法

  • 通过 new Promise(executor) 创建
  • executor 接收 resolvereject 两个参数
  • 使用 .then().catch().finally() 处理结果

4. 静态方法

  • Promise.all():所有 Promise 成功才成功
  • Promise.race():返回最快完成的 Promise
  • Promise.allSettled():等待所有 Promise 完成(无论成功失败)
  • Promise.resolve() / Promise.reject():快速创建已决 Promise

详细解答

1. Promise 基本用法

const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Operation completed successfully');
    } else {
      reject(new Error('Operation failed'));
    }
  }, 1000);
});

// 处理结果
promise
  .then(value => {
    console.log('Success:', value);
  })
  .catch(error => {
    console.error('Error:', error.message);
  })
  .finally(() => {
    console.log('Cleanup code here');
  });

2. 状态流转

const promise = new Promise((resolve, reject) => {
  console.log(promise); // 无法访问,此时 promise 还未赋值

  // pending 状态
  setTimeout(() => {
    resolve('success'); // 变为 fulfilled 状态
    // 或者
    // reject(new Error('failure')); // 变为 rejected 状态

    // 状态一旦确定就不可改变
    resolve('again'); // 被忽略
    reject(new Error('again')); // 被忽略
  }, 1000);
});

// 状态变化是单向的:pending -> fulfilled 或 pending -> rejected

3. 实例方法

then()

const promise = Promise.resolve(1);

promise
  .then(value => {
    console.log(value); // 1
    return value * 2;
  })
  .then(value => {
    console.log(value); // 2
    return value * 2;
  })
  .then(value => {
    console.log(value); // 4
  });

// then 返回新的 Promise,支持链式调用
// 如果返回普通值,会被包装成 resolved Promise
// 如果返回 Promise,会等待其完成
// 如果抛出错误,会被包装成 rejected Promise

catch()

Promise.reject(new Error('fail'))
  .then(value => {
    // 不会执行
  })
  .catch(error => {
    console.error(error.message); // 'fail'
    return 'recovered';
  })
  .then(value => {
    console.log(value); // 'recovered' - 可以继续链式调用
  });

// catch 等价于 then(null, onRejected)
promise.then(null, error => {
  console.error(error);
});

finally()

Promise.resolve('success')
  .then(value => {
    console.log(value);
  })
  .finally(() => {
    // 无论成功失败都会执行
    console.log('Cleanup');
  });

// finally 不接收参数,它的返回值会被忽略(除非抛出错误)

4. 静态方法

Promise.all()

// 所有 Promise 都成功时返回成功的数组
const promises = [
  fetch('/api/user'),
  fetch('/api/posts'),
  fetch('/api/comments')
];

Promise.all(promises)
  .then(([user, posts, comments]) => {
    // 所有请求都成功
  })
  .catch(error => {
    // 任一请求失败
  });

// 注意:如果传入空数组,立即返回 resolved Promise
Promise.all([]).then(values => {
  console.log(values); // []
});

Promise.race()

// 返回最快完成的 Promise(无论成功失败)
const timeout = new Promise((_, reject) => {
  setTimeout(() => reject(new Error('Timeout')), 5000);
});

const fetchData = fetch('/api/data');

Promise.race([fetchData, timeout])
  .then(response => {
    // fetch 先完成
  })
  .catch(error => {
    // timeout 先完成或 fetch 失败
  });

Promise.allSettled()

// 等待所有 Promise 完成,无论成功失败
const promises = [
  Promise.resolve('success'),
  Promise.reject(new Error('failure')),
  Promise.resolve('another success')
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('Success:', result.value);
      } else {
        console.log('Failure:', result.reason);
      }
    });
  });

// 结果格式:
// [
//   { status: 'fulfilled', value: 'success' },
//   { status: 'rejected', reason: Error },
//   { status: 'fulfilled', value: 'another success' }
// ]

Promise.any() (ES2021)

// 返回第一个成功的 Promise,如果全部失败返回 AggregateError
const promises = [
  fetch('/api/primary'),
  fetch('/api/backup1'),
  fetch('/api/backup2')
];

Promise.any(promises)
  .then(response => {
    // 第一个成功的响应
  })
  .catch(error => {
    // 所有 Promise 都失败
    console.log(error.errors); // 所有错误组成的数组
  });

Promise.resolve() / reject()

// 快速创建 resolved Promise
Promise.resolve('value');
// 等价于
new Promise(resolve => resolve('value'));

// 将 thenable 转为 Promise
const thenable = {
  then(resolve, reject) {
    resolve(42);
  }
};
Promise.resolve(thenable).then(value => {
  console.log(value); // 42
});

// 快速创建 rejected Promise
Promise.reject(new Error('error'));
// 等价于
new Promise((_, reject) => reject(new Error('error')));

深入理解

1. Promise 解决了什么问题

回调地狱(Callback Hell)

// 回调地狱示例
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      getMoreData(c, function(d) {
        console.log(d);
      });
    });
  });
});

// Promise 解决方案
getData()
  .then(a => getMoreData(a))
  .then(b => getMoreData(b))
  .then(c => getMoreData(c))
  .then(d => console.log(d))
  .catch(error => console.error(error));

// async/await(基于 Promise)
async function fetchData() {
  try {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getMoreData(b);
    const d = await getMoreData(c);
    console.log(d);
  } catch (error) {
    console.error(error);
  }
}

错误处理

// 回调的错误处理复杂
async1(function(error, result1) {
  if (error) {
    handleError(error);
    return;
  }
  async2(result1, function(error, result2) {
    if (error) {
      handleError(error);
      return;
    }
    // ...
  });
});

// Promise 统一错误处理
async1()
  .then(result1 => async2(result1))
  .then(result2 => async3(result2))
  .catch(error => handleError(error)); // 捕获链中任何错误

2. Promise 的链式调用机制

// then 返回新的 Promise,支持链式调用
const promise = Promise.resolve(1);

const promise2 = promise.then(value => value * 2);
// promise2 是新的 Promise,不是原来的 promise

// 返回值的类型决定新 Promise 的状态
Promise.resolve(1)
  .then(v => v * 2)           // 返回普通值 -> resolved
  .then(v => Promise.resolve(v * 2))  // 返回 Promise -> 等待其完成
  .then(v => { throw new Error(); })  // 抛出错误 -> rejected
  .catch(e => 'recovered')    // 捕获错误,返回普通值 -> resolved
  .then(v => console.log(v)); // 'recovered'

3. Promise 的执行时机

console.log('1');

const promise = new Promise((resolve) => {
  console.log('2');
  resolve('3');
});

promise.then(value => console.log(value));

console.log('4');

// 输出顺序:1, 2, 4, 3
// Promise 构造函数是同步执行的
// then/catch/finally 的回调是异步执行的(微任务)

4. Promise 与微任务

console.log('Start');

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

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

console.log('End');

// 输出顺序:
// Start
// End
// Promise 1
// Promise 2
// Timeout

// 解释:Promise 回调是微任务,在宏任务(setTimeout)之前执行

5. Promise 的局限性

// 1. 一旦创建无法取消
const promise = new Promise((resolve) => {
  setTimeout(() => resolve('done'), 5000);
});
// 无法取消这个 5 秒的等待

// 2. 不设置回调函数,内部错误不会反应到外部
const promise2 = new Promise((resolve, reject) => {
  reject(new Error('error'));
});
// 如果没有 catch,错误会被静默处理(可能报错)

// 3.  pending 状态时无法得知进度
// Promise 只有 pending/fulfilled/rejected 三种状态
// 无法获取进行中的进度信息

// 4. 错误捕获的坑
Promise.resolve()
  .then(() => {
    throw new Error('error in then');
  })
  .catch(error => {
    console.log('Caught:', error.message);
    throw new Error('error in catch');
  })
  .catch(error => {
    console.log('Caught again:', error.message);
  });

使用场景

1. 图片加载

function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`Failed to load ${src}`));
    img.src = src;
  });
}

// 使用
loadImage('https://example.com/image.jpg')
  .then(img => {
    document.body.appendChild(img);
  })
  .catch(error => {
    console.error('Image load failed:', error);
    // 显示占位图
  });

// 加载多张图片
const imageUrls = ['1.jpg', '2.jpg', '3.jpg'];
Promise.all(imageUrls.map(loadImage))
  .then(images => {
    images.forEach(img => document.body.appendChild(img));
  });

2. 链式操作

// 用户登录流程
function login(credentials) {
  return fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify(credentials)
  }).then(res => res.json());
}

function getUserProfile(userId) {
  return fetch(`/api/users/${userId}`).then(res => res.json());
}

function getUserPermissions(userId) {
  return fetch(`/api/users/${userId}/permissions`).then(res => res.json());
}

// 链式调用
login({ username, password })
  .then(user => {
    saveToken(user.token);
    return getUserProfile(user.id);
  })
  .then(profile => {
    updateUI(profile);
    return getUserPermissions(profile.id);
  })
  .then(permissions => {
    configureAccess(permissions);
  })
  .catch(error => {
    showError(error.message);
  });

3. 请求合并

// 并行请求多个资源
function loadDashboardData() {
  const userPromise = fetch('/api/user').then(r => r.json());
  const statsPromise = fetch('/api/stats').then(r => r.json());
  const notificationsPromise = fetch('/api/notifications').then(r => r.json());

  return Promise.all([
    userPromise,
    statsPromise,
    notificationsPromise
  ]).then(([user, stats, notifications]) => ({
    user,
    stats,
    notifications
  }));
}

// 使用
loadDashboardData()
  .then(data => renderDashboard(data))
  .catch(error => showError('Failed to load dashboard'));

4. 超时处理

function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) => {
      setTimeout(() => reject(new Error('Request timeout')), timeout);
    })
  ]);
}

// 使用
fetchWithTimeout('/api/data', {}, 3000)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.message === 'Request timeout') {
      console.log('Request took too long');
    } else {
      console.log('Request failed:', error);
    }
  });

5. 重试机制

function fetchWithRetry(url, options = {}, maxRetries = 3) {
  return fetch(url, options).catch(error => {
    if (maxRetries <= 0) {
      throw error;
    }
    console.log(`Retrying... ${maxRetries} attempts left`);
    return fetchWithRetry(url, options, maxRetries - 1);
  });
}

// 带延迟的重试
function fetchWithRetryAndDelay(url, options = {}, maxRetries = 3, delay = 1000) {
  return fetch(url, options).catch(error => {
    if (maxRetries <= 0) {
      throw error;
    }
    return new Promise(resolve => setTimeout(resolve, delay))
      .then(() => fetchWithRetryAndDelay(url, options, maxRetries - 1, delay));
  });
}

6. 顺序执行异步任务

// 顺序执行一组异步任务
const urls = ['/api/1', '/api/2', '/api/3'];

// 方式1:使用 reduce
urls.reduce((promise, url) => {
  return promise.then(results => {
    return fetch(url)
      .then(r => r.json())
      .then(data => [...results, data]);
  });
}, Promise.resolve([]))
.then(allResults => console.log(allResults));

// 方式2:使用 async/await
async function fetchSequentially(urls) {
  const results = [];
  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.json();
    results.push(data);
  }
  return results;
}

7. 缓存 Promise

class PromiseCache {
  constructor() {
    this.cache = new Map();
  }

  get(key, factory) {
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }

    const promise = factory().finally(() => {
      // 可选:请求完成后从缓存中移除
      // this.cache.delete(key);
    });

    this.cache.set(key, promise);
    return promise;
  }

  clear() {
    this.cache.clear();
  }
}

// 使用
const cache = new PromiseCache();

function getUser(userId) {
  return cache.get(`user-${userId}`, () => {
    return fetch(`/api/users/${userId}`).then(r => r.json());
  });
}

// 多次调用返回同一个 Promise
getUser(1).then(user => console.log(user));
getUser(1).then(user => console.log(user)); // 使用缓存

最佳实践

1. 始终返回 Promise

// 推荐:函数始终返回 Promise
function getData() {
  if (cache.hasData()) {
    return Promise.resolve(cache.getData());
  }
  return fetch('/api/data').then(r => r.json());
}

// 不推荐:有时返回 Promise,有时返回值
function getDataBad() {
  if (cache.hasData()) {
    return cache.getData(); // 返回值
  }
  return fetch('/api/data'); // 返回 Promise
}

2. 错误处理

// 推荐:在链式末尾统一处理错误
fetchData()
  .then(processData)
  .then(saveData)
  .catch(handleError);

// 推荐:在可能出错的地方分别处理
fetchData()
  .then(data => {
    try {
      return processData(data);
    } catch (error) {
      return defaultData;
    }
  })
  .then(saveData)
  .catch(handleError);

// 不推荐:忽略错误
fetchData().then(data => console.log(data));
// 如果 fetchData 失败,错误会被静默处理

3. 避免 Promise 构造函数反模式

// 反模式:包装已经是 Promise 的值
function bad() {
  return new Promise((resolve, reject) => {
    fetch('/api/data')
      .then(response => resolve(response))
      .catch(error => reject(error));
  });
}

// 正确:直接返回 Promise
function good() {
  return fetch('/api/data');
}

// 反模式:同步代码使用 Promise
function bad2() {
  return new Promise((resolve) => {
    const result = someSyncOperation();
    resolve(result);
  });
}

// 正确:使用 Promise.resolve()
function good2() {
  return Promise.resolve(someSyncOperation());
}

4. 使用 async/await

// Promise 链式调用
fetchUser()
  .then(user => fetchOrders(user.id))
  .then(orders => processOrders(orders))
  .catch(error => handleError(error));

// async/await 更清晰
async function loadUserOrders() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    return processOrders(orders);
  } catch (error) {
    return handleError(error);
  }
}

5. 并发控制

// 限制并发数量
async function fetchWithConcurrencyLimit(urls, limit = 5) {
  const results = [];
  const executing = [];

  for (const [index, url] of urls.entries()) {
    const promise = fetch(url)
      .then(r => r.json())
      .then(data => ({ index, data }));

    results.push(promise);

    if (urls.length >= limit) {
      executing.push(promise);
      if (executing.length >= limit) {
        await Promise.race(executing);
        executing.splice(executing.findIndex(p => p === promise), 1);
      }
    }
  }

  return Promise.all(results);
}

面试要点

  1. Promise 状态

    • 三种状态:pendingfulfilledrejected
    • 状态一旦改变不可再变
    • 理解状态流转的时机
  2. then 的链式调用

    • then 返回新的 Promise
    • 返回值类型决定新 Promise 状态
    • 错误会沿着链向下传递直到被捕获
  3. 静态方法的区别

    • Promise.all:所有成功才成功,一个失败就失败
    • Promise.race:返回最快完成的(无论成功失败)
    • Promise.allSettled:等待所有完成,返回状态数组
    • Promise.any:返回第一个成功的(ES2021)
  4. 错误处理

    • catch 可以捕获链中任何位置的错误
    • 没有 catch 的错误可能被静默处理
    • finally 不接收参数,返回值被忽略
  5. Promise 与微任务

    • Promise 回调是微任务
    • 微任务优先级高于宏任务(setTimeout/setInterval)
    • 理解事件循环中 Promise 的执行时机
  6. Promise 的局限性

    • 无法取消
    • 无法获取进度
    • 不设置回调时错误可能被静默处理
  7. 实际应用场景

    • 图片/资源加载
    • 请求合并与并发控制
    • 超时处理
    • 重试机制
    • 链式异步操作
  8. async/await 与 Promise

    • async/await 是基于 Promise 的语法糖
    • async 函数返回 Promise
    • await 会暂停执行,等待 Promise 完成
    • 使用 try/catch 处理错误