🚀Promise 从入门到手写:核心方法实现全指南

Promise的介绍、原理和手写

一、Promise 的诞生:解决回调地狱的终极方案

在 JavaScript 的异步编程发展史上,回调函数曾是处理异步操作的主要手段。但随着业务逻辑的复杂化,回调嵌套的层数越来越深,形成了令人头疼的 "回调地狱"。例如:

TypeScript 复制代码
fs.readFile('a.txt', 'utf8', (err, data1) => {

    fs.readFile(`./${data1}.txt', 'utf8', (err, data2) => {

        fs.readFile(`./${data2}.txt', 'utf8', (err, data3) => {

            // 深层嵌套的回调逻辑, 从这开始如果想处理异常就已经有点麻烦了

        });

    });

});

这种代码结构不仅可读性差,维护成本高,而且异常处理困难。Promise 的出现正是为了规范异步编程,通过链式调用让异步逻辑更清晰,状态管理更可控。ECMAScript 6 正式将 Promise 纳入标准,使其成为现代 JavaScript 异步编程的基石。

二、Promise 核心概念解析

(一)Promise 的三种状态

  1. pending(进行中):初始状态,异步操作未完成或未拒绝

  2. fulfilled(已完成):异步操作成功完成

  3. rejected(已拒绝):异步操作失败

状态转换具有单向性:pending可以转换为fulfilled或rejected,但一旦确定状态就无法再改变。

(二)Promise 构造函数

TypeScript 复制代码
const promise = new Promise((resolve, reject) => {

    // 执行异步操作

    setTimeout(() => {

        const success = true;

        if (success) {

            resolve('操作成功'); // 状态转为fulfilled

        } else {

            reject('操作失败'); // 状态转为rejected

        }

    }, 1000);

});
  • resolve:成功时调用的回调函数,参数为成功状态的返回值

  • reject:失败时调用的回调函数,参数为失败状态的原因

构造函数的手写
Javascript 复制代码
class MyPromise {

    constructor(executor) {

        this.state = 'pending';

        this.value = undefined;

        this.fulfilledCallbacks = [];

        this.rejectedCallbacks = [];



        const resolve = (value) => {

            if (this.state !== 'pending') return;

            this.state = 'fulfilled';

            this.value = value;

            // 异步执行回调(模拟微任务)

            setTimeout(() => {

                this.fulfilledCallbacks.forEach(fn => fn(value));

            }, 0);

        };



        const reject = (reason) => {

            if (this.state !== 'pending') return;

            this.state = 'rejected';

            this.value = reason;

            setTimeout(() => {

                this.rejectedCallbacks.forEach(fn => fn(reason));

            }, 0);

        };



        try {

            executor(resolve, reject);

        } catch (error) {

        }

    }

(三)关键方法详解

then () - 异步结果处理
TypeScript 复制代码
promise.then(

    (result) => { // fulfilled状态处理函数

        console.log(result); // 输出"操作成功"

    }, 

    (error) => { // rejected状态处理函数

        console.error(error);

    }

);
then方法的手写
JavaScript 复制代码
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    // 参数默认值处理
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };

    const newPromise = new MyPromise((resolve, reject) => {
        if (this.state === 'fulfilled') {
            // 立即执行,返回值决定新Promise状态​
            setTimeout(() => {
                try {
                    const x = onFulfilled(this.value);
                    resolvePromise(newPromise, x, resolve, reject); // 核心解析函数
                } catch (error) {
                    reject(error);
                }
            }, 0);
        }

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

    }​
  }
catch () - 统一错误处理
TypeScript 复制代码
promise.then(result => {

// 成功处理逻辑

}).catch(error => {

// 统一处理所有拒绝状态

console.error('发生错误:', error);

});
finally () - 最终执行逻辑
TypeScript 复制代码
promise

.then(result => process(result))

.catch(error => handleError(error))

.finally(() => {

// 无论成功或失败都会执行

console.log('异步操作结束');

});

三、Promise 链式调用的最佳实践

(一)链式调用的实现原理

每次调用then()或catch()都会返回一个新的 Promise 对象,从而实现链式调用。新 Promise 的状态由以下规则决定:

  • 若回调函数返回有效值,新 Promise 状态为fulfilled,值为返回值

  • 若回调函数抛出错误,新 Promise 状态为rejected,值为错误对象

  • 若回调函数返回另一个 Promise,新 Promise 状态由该 Promise 决定

(二)典型应用场景

1. 顺序执行异步操作
TypeScript 复制代码
fetchUser()

    .then(user => fetchOrder(user.id))

    .then(order => processOrder(order))

    .catch(error => logError(error));
2. 错误处理策略
TypeScript 复制代码
asyncFunction()

    .then(result => {

        // 可能抛出错误的操作

        if (result.invalid) {

            throw new Error('无效结果');

        }

        return process(result);

    })

    .catch(error => {

        // 可以针对不同错误类型处理

        if (error instanceof NetworkError) {

            retryRequest();

        } else if (error.message === '无效结果') {

            showValidationError();

        }

    });

四、高级特性与静态方法

Promise.all () - 并行执行多个异步操作

TypeScript 复制代码
const promise1 = fetch('https://api/data1');

const promise2 = fetch('https://api/data2');



Promise.all([promise1, promise2])

    .then(responses => responses.map(res => res.json()))

    .then(data => console.log('全部数据:', data))

    .catch(error => console.error('任一请求失败:', error));
  • 输入:Promise 数组

  • 输出:包含所有 Promise 结果的数组

  • 特点:只要有一个 Promise 拒绝,整体就拒绝

Promise.all()的手写
JavaScript 复制代码
MyPromise.all = function (promises) {
    return new MyPromise((resolve, reject) => {
        const results = [];
        let count = 0;
        promises.forEach((p, index) => {
            MyPromise.resolve(p).then((value) => {
                results[index] = value;
                if (++count === promises.length) {
                    resolve(results);
                }
            }, reject); // 第一个失败立即触发reject
        });
    });
};

Promise.race () - 竞争执行

TypeScript 复制代码
const timeout = new Promise((_, reject) => {

    setTimeout(() => reject(new Error('请求超时')), 5000);

});



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

Promise.race([fetchData, timeout])

    .then(data => process(data))

    .catch(error => handleError(error));
  • 输入:Promise 数组

  • 输出:第一个解决(fulfilled/rejected)的 Promise 结果

Promise.race () 的手写
ini 复制代码
MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
        promises.forEach(p => {
            MyPromise.resolve(p).then(resolve, reject);
        });
    });
};

Promise.allSettled () - 获取所有结果(包括拒绝)

TypeScript 复制代码
const promises = [

    Promise.resolve(1),

    Promise.reject('失败'),

    Promise.resolve(3)

];



Promise.allSettled(promises)

    .then(results => {

        results.forEach(result => {

            if (result.status === 'fulfilled') {

                console.log('成功值:', result.value);

            } else {

                console.error('失败原因:', result.reason);

            }

        });

    });
Promise.allSettled ()的手写
JavaScript 复制代码
MyPromise.allSettled = function(promises) {
    return new MyPromise((resolve) => {
      const results = [];
      let count = 0;
      promises.forEach((p, index) => {
        MyPromise.resolve(p).then(
          (value) => {
            results[index] = { status: 'fulfilled', value };
            if (++count === promises.length) resolve(results);
          },
          (reason) => {
            results[index] = { status: 'rejected', reason };
            if (++count === promises.length) resolve(results);
          }
        );
      });
    });
  };

(四)Promise.any () - 获取第一个成功结果

TypeScript 复制代码
const promises = [

    Promise.reject('失败1'),

    Promise.resolve('成功'),

    Promise.reject('失败2')

];



Promise.any(promises)

    .then(value => console.log('第一个成功值:', value))

    .catch(error => console.error('所有都失败:', error));

五、Promise 与 async/await 的完美结合

(一)语法糖带来的代码简化

传统 Promise 写法:

TypeScript 复制代码
fetchUser()

    .then(user => fetchProfile(user.id))

    .then(profile => saveProfile(profile))

    .catch(error => handleError(error));

async/await 写法:

TypeScript 复制代码
async function saveUserProfile() {

    try {

        const user = await fetchUser();

        const profile = await fetchProfile(user.id);

        await saveProfile(profile);

    } catch (error) {

        handleError(error);

    }

}

(二)错误处理机制

  • try/catch捕获整个异步块的错误

  • 单个await表达式可以用try/catch单独处理

  • 保持与 Promise 相同的错误传递机制

六、最佳实践与注意事项

(一)错误处理原则

  1. 始终添加catch处理程序或使用try/catch

  2. 避免未处理的 Promise 拒绝(可通过全局监听unhandledrejection事件)

  3. 在链式调用中保持错误处理的连续性

(二)性能优化

  1. 避免创建不必要的 Promise 对象

  2. 合理使用并行执行(Promise.all)替代顺序执行

  3. 注意内存管理,及时释放不再需要的 Promise 引用

(三)常见反模式

TypeScript 复制代码
// 反模式:在then中返回非Promise值时未正确处理

somePromise.then(() => {

    return makeRequest(); // 假设makeRequest不是Promise

}).then(result => { /* 这里result会是undefined */ });



// 正确做法:确保返回Promise或有效值

somePromise.then(() => {

    return Promise.resolve(makeRequest());

}).then(result => { /* 正确获取结果 */ });

七、Promise 的浏览器兼容性

兼容性现状

  • 现代浏览器(Chrome, Firefox, Edge, Safari 10+)完全支持 Promise

  • IE 浏览器需要 polyfill(如 es6-promise 库)

八、总结

当然,现在前端卷成这样的环境下,掌握 Promise 早已不是前端开发者的加分项,很多时候它只是面试八股里的一道例题,但这不代表它不重要。

它不仅是一种技术实现,更代表着一套完整的异步思维模式 ------ 当你能熟练运用状态管理、结果传递和流程控制这三大核心武器时,再复杂的异步难题也会迎刃而解。

Promise 的出现标志着 JavaScript 异步编程的成熟,它通过标准化的状态管理和链式调用机制,解决了回调地狱的难题。结合 async/await 语法糖,使得异步代码可以写成同步风格,极大提升了代码的可读性和可维护性。

建议大伙在实际项目中多使用 Promise 进行异步操作封装,遵循最佳实践,也愿诸位写出更优雅、更健壮的代码。

相关推荐
中微子34 分钟前
React 状态管理 源码深度解析
前端·react.js
加减法原则2 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele2 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4532 小时前
React移动端开发项目优化
前端·react.js·前端框架
你的人类朋友2 小时前
🍃Kubernetes(k8s)核心概念一览
前端·后端·自动化运维
web_Hsir2 小时前
vue3.2 前端动态分页算法
前端·算法
烛阴3 小时前
WebSocket实时通信入门到实践
前端·javascript
草巾冒小子3 小时前
vue3实战:.ts文件中的interface定义与抛出、其他文件的调用方式
前端·javascript·vue.js
追逐时光者3 小时前
面试第一步,先准备一份简洁、优雅的简历模板!
后端·面试
DoraBigHead3 小时前
你写前端按钮,他们扛服务器压力:搞懂后端那些“黑话”!
前端·javascript·架构