《前端面试题:Promise指南》

JavaScript Promise 指南:从基础到高级实践

一、Promise 基础概念

1. 什么是 Promise?

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

  1. pending(待定):初始状态,既不是成功也不是失败
  2. fulfilled(已兑现):操作成功完成
  3. rejected(已拒绝):操作失败
// Promise 基本结构
const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 操作成功 */) {
    resolve(value); // 状态变为 fulfilled
  } else {
    reject(error); // 状态变为 rejected
  }
});

2. Promise 的特点

  • 不可逆性:状态一旦改变(从 pending 到 fulfilled 或 rejected),就不能再变
  • 链式调用:通过 .then().catch() 实现链式调用
  • 值穿透:如果 then 中不传函数,值会穿透到后面的 then
  • 错误冒泡:错误会一直向后传递,直到被捕获

二、Promise 核心方法

1. 实例方法

.then(onFulfilled, onRejected)
fetchData()
  .then(
    data => console.log('成功:', data),  // 成功回调
    error => console.error('失败:', error) // 失败回调(可选)
  );
.catch(onRejected)
fetchData()
  .then(data => processData(data))
  .catch(error => console.error('捕获错误:', error));
.finally(onFinally)
showLoading();
fetchData()
  .then(data => render(data))
  .catch(error => showError(error))
  .finally(() => hideLoading()); // 无论成功失败都会执行

2. 静态方法

Promise.resolve(value)
// 创建一个立即 resolve 的 Promise
Promise.resolve('立即值')
  .then(value => console.log(value)); // "立即值"
Promise.reject(reason)
// 创建一个立即 reject 的 Promise
Promise.reject(new Error('失败'))
  .catch(err => console.error(err)); // Error: 失败
Promise.all(iterable)
// 等待所有 Promise 完成(全部成功才算成功)
Promise.all([promise1, promise2, promise3])
  .then(values => console.log(values)) // [val1, val2, val3]
  .catch(error => console.error(error)); // 任一失败立即 reject
Promise.race(iterable)
// 竞速,第一个 settled 的 Promise 决定结果
Promise.race([promise1, promise2])
  .then(value => console.log('第一个完成:', value))
  .catch(error => console.error('第一个失败:', error));
Promise.allSettled(iterable) (ES2020)
// 等待所有 Promise 完成,不论成功失败
Promise.allSettled([promise1, promise2])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('成功:', result.value);
      } else {
        console.error('失败:', result.reason);
      }
    });
  });

三、Promise 高级应用

1. Promise 链式调用

login(user)
  .then(token => getUserInfo(token))
  .then(userInfo => getOrders(userInfo.id))
  .then(orders => renderOrders(orders))
  .catch(error => showError(error));

2. Promise 错误处理

// 方式1:每个 then 都处理错误
fetchData()
  .then(
    data => process(data),
    error => handleError(error)
  );

// 方式2:统一 catch 处理
fetchData()
  .then(data => process(data))
  .then(moreData => processMore(moreData))
  .catch(error => handleError(error));

3. Promise 与 async/await

async function fetchUserData() {
  try {
    const token = await login(user);
    const userInfo = await getUserInfo(token);
    const orders = await getOrders(userInfo.id);
    return renderOrders(orders);
  } catch (error) {
    showError(error);
    throw error; // 可以选择继续抛出
  }
}

4. 实现 Promise 超时控制

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

四、Promise 常见面试题

1. 实现一个简单的 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);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        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 解决过程
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }
  
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

2. Promise.all 的实现

Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;
    
    if (promises.length === 0) {
      resolve(results);
      return;
    }

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          results[index] = value;
          completed++;
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
};

3. 如何取消一个 Promise?

JavaScript 原生 Promise 无法直接取消,但可以通过以下模式实现:

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

// 使用示例
const cp = cancellablePromise((resolve, reject) => {
  setTimeout(() => resolve('Done'), 2000);
});

cp.then(console.log).catch(console.error);
cp.cancel(); // 取消 Promise

4. 解释 Promise 链中的值穿透

Promise.resolve('foo')
  .then(Promise.resolve('bar')) // 这里传入的不是函数!
  .then(value => console.log(value)); // 输出什么?

答案:输出 'foo'。因为 .then() 期望接收函数作为参数,如果传递的不是函数,Promise 会执行"值穿透",直接将上一个 Promise 的值传递下去。

5. 实现一个 Promise 重试机制

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

// 使用示例
retry(() => fetch('https://api.example.com'), 3, 1000)
  .then(console.log)
  .catch(console.error);

五、Promise 最佳实践

  1. 总是返回 Promise:在 then 回调中返回 Promise 或值,确保链式调用

    // 好的做法
    fetchData()
      .then(data => process(data))
      .then(processed => save(processed));
    
    // 不好的做法
    fetchData()
      .then(data => {
        process(data); // 没有返回
      })
      .then(processed => { // processed 是 undefined
        save(processed);
      });
    
  2. 永远捕获错误:使用 .catch()try/catch 处理错误

    // 方式1
    fetchData()
      .then(process)
      .catch(handleError);
    
    // 方式2 (async/await)
    try {
      const data = await fetchData();
      const result = await process(data);
    } catch (error) {
      handleError(error);
    }
    
  3. 避免 Promise 嵌套:使用链式调用而非嵌套

    // 好的做法
    fetchData()
      .then(process)
      .then(save)
      .then(log);
    
    // 不好的做法
    fetchData().then(data => {
      process(data).then(processed => {
        save(processed).then(() => {
          log();
        });
      });
    });
    
  4. 合理使用 Promise 静态方法

    • Promise.all:并行执行多个独立操作
    • Promise.race:超时控制或竞速场景
    • Promise.allSettled:需要知道所有操作结果时
  5. 避免同步代码中的 Promise:Promise 设计用于处理异步操作

六、Promise 与事件循环

Promise 的回调作为**微任务(microtask)执行,优先级高于宏任务(macrotask)**如 setTimeout:

console.log('Script start');

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

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

console.log('Script end');

// 输出顺序:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout

七、Promise 的现代替代方案

虽然 Promise 是处理异步操作的基础,但现代 JavaScript 提供了更高级的抽象:

1. async/await

async function getUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error('Failed to load data:', error);
    throw error;
  }
}

2. Observable (RxJS)

import { from } from 'rxjs';

from(fetch('https://api.example.com/data'))
  .pipe(
    map(response => response.json()),
    retry(3),
    catchError(error => of({ error: true }))
  )
  .subscribe(data => console.log(data));

总结:Promise 核心要点

  1. 状态不可逆:pending → fulfilled 或 pending → rejected
  2. 链式调用.then() 返回新 Promise,支持链式操作
  3. 错误冒泡:错误会一直向后传递,直到被 .catch() 捕获
  4. 微任务队列:Promise 回调在微任务队列执行,优先级高于宏任务
  5. 现代异步基础:async/await 基于 Promise 实现

掌握 Promise 的重要性
Promise 是现代 JavaScript 异步编程的基石,理解 Promise 的工作原理和使用模式,是成为高级 JavaScript 开发者的必备技能。通过合理运用 Promise 链、错误处理和组合方法,可以构建出健壮、可维护的异步代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

站在风口的猪1108

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值