【JavaScript】详解Promise(含手撕代码)

什么是Promise

从本质上说,Promise 是一个用来处理异步操作的对象。它代表一个“未来才会知道结果”的操作 —— 成功某个值得到结果,失败某个理由出错

Promise的三种状态

Promise 有三种状态(状态机):

状态含义
pending(等待中)异步操作还没完成,也没失败,还未定论
fulfilled(已成功)异步操作成功,得到了一个值
rejected(已失败)异步操作失败,有一个错误原因

一旦从 pending 转到 fulfilled 或 rejected,状态就固定了,不会再变

Promise 的构造与用法

构造

创建一个 Promise 的基本形式是:

let p = new Promise((resolve, reject) => {
  // 执行一些异步任务
  if (成功) {
    resolve(value);
  } else {
    reject(error);
  }
});

这里的要点:

  • executor 函数会被同步执行,也就是说在构造 Promise 的时候,executor 的代码会立刻跑(除非内部又异步,例如 setTimeout 等)
  • resolve(value):将 Promise 状态从 pending 变为 fulfilled,value 是最终值。
  • reject(reason):将 Promise 状态变为 rejected,reason 通常是一个 Error 对象或其它说明失败的值。

Promise.prototype.then(onFulfilled, onRejected)

then方法返回的是一个新的Promise实例

.then(onFulfilled, onRejected?)

  • 第一个参数是成功时处理函数
  • 第二个是失败时处理函数(可选)

优点:

  • 避免 回调 地狱(callback 嵌套太深)
  • 逻辑更清晰,每一步处理一个任务

链式调用(Chaining)

.then() 会返回一个新的 Promise,所以可以链式地写多个 .then

此外,采用链式的then,可以指定一组按照指定次序调用的回调函数。因为后一个回调函数会等待上一个Promise实例对象的状态发生变化再被调用

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .catch(err => handleError(err));

Promise.prototype.catch(onRejected)

等同于 .then(null, onRejected),用来处理失败情况

Promise.prototype.finally(onFinally)

无论成功或失败,都会执行,用来做清理之类的工作

静态方法

resolverejectallraceallSettledany

Promise.resolve(value)

创建一个立即被fufilled的promise对象

thenable: 是一个对象(object/也可能是函数类的对象)具有 .then(...) 方法的东西。simple as that.

Promise.reject(reason)

返回一个立刻 rejected 的 Promise

reject 是JavaScript Promise 中表示异步操作失败的函数,通过 new Promise((resolve, reject) => { ... reject(reason) ... }) 的形式在异步操作失败时调用,将Promise 的状态从 pending 变为 rejected。此外,Promise.reject(reason) 是一个静态方法,用于快速创建一个已拒绝的Promise 对象,其中 reason 是Promise 拒绝的原因

Promise.all(iterable)

并行等待全部完成;只要有一个失败就立刻失败

注意:

  1. 只要有一个 reject,整个 all 直接 reject,看不到其它成功结果;需要“全量结果”请用 allSettled
  2. 返回值是按输入顺序排列的结果数组,与完成先后无关

Promise.race(iterable)

返回第一个完成的promise结果,无论是成功还是失败;常用于超时

时序细节:

  • 空 iterable:返回的 Promise 会永远 pending
  • 包含已 settle 的值/非 Promise 值:会取到迭代中遇到的第一个(同一 tick 内按迭代顺序“不公平”地选择)
// 举例
const data = await Promise.race([
  fetch('/api'),
  new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), 5000))
]);
// 先完成要么是 fetch,要么是 5 秒后超时

Promise.allSettled(iterable)

等待所有 Promise 完成(无论成功还是失败)

为每个 Promise 创建结果对象,包含 status 和对应的 value 或 reason:

  • 成功项:{ status: 'fulfilled', value }

  • 失败项:{ status: 'rejected', reason }

    不会 reject。空 iterable 也直接 fulfilled(值为 []

适用场景:你要收集全部结果做汇总/局部容错,而不是“一票否决”

Promise.any(iterable)

等到第一个fulfilled就成功,忽略之前所有的reject

全部都 rejected,或传入 iterable,则以 AggregateError 拒绝,并包含所有失败原因

race 的区别:race 只看谁先 settle(成功或失败都可能“赢”);any 只要第一个成功,失败不算“赢”

对比

组合器何时 fulfilled何时 rejected典型用途
all全部成功任何一个失败(立刻需要所有结果且强一致
allSettled全部 settle 后一次性返回快照数组不会收集成功/失败明细、容错汇总
race第一个settle第一个settle(失败也算)竞速、超时(和计时器比跑)
any第一个成功全部失败AggregateError“谁先成功用谁” 的容错抓取

与 async / await 的关系

Promise 是底层机制,async / await 是语法糖,让异步的代码看起来像同步:

async function foo() {
  try {
    let result = await doSomething(); // 等待 doSomething 的 Promise resolve 或 reject
    let more = await doSomethingElse(result);
    return more;
  } catch (e) {
    // 捕获 reject
    console.error(e);
    throw e; // 如果希望继续传播错误
  }
}
  • async 函数返回一个 Promise
  • await 后面通常跟一个 Promise,await 会“暂停”当前 async 函数的执行,直到 Promise settle。
  • 错误处理可以像同步代码用 try/catch。比起纯 .then().catch() 链子更清晰

面试常考手撕题

手动实现 resolve/reject

class MyPromise {
    constructor(executor){
        // 初始状态为pending
        this.state = "pending";
        this.value = undefined; // 成功时的返回
        this.reason = undefined; // 失败的原因
        this.onFulfilledCallbacks = []; //存储成功的回调
        this.onRejectedCallbacks = []; // 存储失败的回调        
				// 内部resove函数
        const resolve = (value) => {
            if(this.state === "pending"){
                this.state = "fulfilled";
                this.value = value;
                this.onFulfilledCallbacks.forEach(callback => callback());
            }
        };
        // 内部reject函数
        const reject = (reason) => {
            if(this.state === "pending"){
                this.state = "rejected";
                this.reason = reason;
                this.onRejectedCallbacks.forEach(callback => callback());
            }
        };

        try{
            executor(resolve, reject);
        }catch(error){
            reject(error);
        }
    }
    // then 方法:注册成功或失败的回调
    then(onFulfilled, onRejected){
        if(this.state === "fulfilled"){
            onFulfilled(this.value);
        }
        if(this.state === "rejected"){
            onRejected(this.reason);
        }
        // 如果状态还在 pending,就把回调存起来,等 resolve 或 reject 被调用时再触发
        if(this.state === "pending"){
            this.onFulfilledCallbacks.push(onFulfilled);
            this.onRejectedCallbacks.push(onRejected);
        }
    }
}
// 应用
const p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      reject("出错啦!");
    }, 1000);
  });
  
  p.then(
    (value) => console.log("成功:", value),
    (reason) => console.log("失败:", reason)
  );
  // 1秒后输出: 失败: 出错啦!

手动实现all

function myPromiseAll(promises){
    return new Promise((resolve, reject) => {
        // 边界情况:如果输入不是数组,直接reject
        if(!Array.isArray(promises)){
            reject(new TypeError('Promise.all 的参数必须是一个数组'));
            return;
        }
        
        // 边界情况:如果数组为空,直接resolve空数组
        if(promises.length === 0){
            resolve([]);
            return;
        }
        
        let result = [];
        let count = 0;
        
        promises.forEach((promise, index) => {
            // 确保每个元素都是Promise
            Promise.resolve(promise).then(res => {
                result[index] = res;  // 保持原始顺序
                count++;
                
                // 关键修复:在每次成功回调中检查是否所有Promise都完成了
                if(count === promises.length){
                    resolve(result);
                }
            }).catch(reject);  // 任何一个Promise失败都会导致整个all失败
        });
    });
}

myPromiseAll([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(res => {
    console.log(res);
}) 

手动实现race

function myPromiseRace(promises) {
    return new Promise((resolve, reject) => {
        // 边界情况:如果输入不是数组,直接reject
        if (!Array.isArray(promises)) {
            reject(new TypeError('Promise.race 的参数必须是一个数组'));
            return;
        }
        
        // 边界情况:如果数组为空,Promise永远不会resolve或reject
        if (promises.length === 0) {
            return; // 返回一个永远pending的Promise
        }
        
        // 遍历所有Promise,第一个完成的(成功或失败)就会触发resolve/reject
        promises.forEach(promise => {
            // 确保每个元素都是Promise
            Promise.resolve(promise)
                .then(resolve)  // 第一个成功的Promise会触发resolve
                .catch(reject); // 第一个失败的Promise会触发reject
        });
    });
}

手动实现any

function myPromiseAny(promises) {
    return new Promise((resolve, reject) => {
        // 边界情况:如果输入不是数组,直接reject
        if (!Array.isArray(promises)) {
            reject(new TypeError('Promise.any 的参数必须是一个数组'));
            return;
        }
        
        // 边界情况:如果数组为空,直接reject
        if (promises.length === 0) {
            reject(new AggregateError([], 'All promises were rejected'));
            return;
        }
        
        let rejectedCount = 0;
        let errors = [];
        
        promises.forEach((promise, index) => {
            // 确保每个元素都是Promise
            Promise.resolve(promise)
                .then(resolve)  // 第一个成功的Promise立即resolve
                .catch(error => {
                    errors[index] = error;
                    rejectedCount++;
                    
                    // 如果所有Promise都失败了,reject with AggregateError
                    if (rejectedCount === promises.length) {
                        reject(new AggregateError(errors, 'All promises were rejected'));
                    }
                });
        });
    });
}

手动实现allsettled

function myPromiseAllSettled(promises) {
    return new Promise((resolve, reject) => {
        // 边界情况:如果输入不是数组,直接reject
        if (!Array.isArray(promises)) {
            reject(new TypeError('Promise.allSettled 的参数必须是一个数组'));
            return;
        }
        
        // 边界情况:如果数组为空,直接resolve空数组
        if (promises.length === 0) {
            resolve([]);
            return;
        }
        
        let result = [];
        let count = 0;
        
        promises.forEach((promise, index) => {
            // 确保每个元素都是Promise
            Promise.resolve(promise)
                .then(res => {
                    // 成功的Promise
                    result[index] = {
                        status: 'fulfilled',
                        value: res
                    };
                    count++;
                    
                    // 当所有Promise都完成时,resolve结果
                    if (count === promises.length) {
                        resolve(result);
                    }
                })
                .catch(error => {
                    // 失败的Promise
                    result[index] = {
                        status: 'rejected',
                        reason: error
                    };
                    count++;
                    
                    // 当所有Promise都完成时,resolve结果
                    if (count === promises.length) {
                        resolve(result);
                    }
                });
        });
    });
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值