一、啥是 Promise

在 JavaScript 里,Promise 就像是咱生活中的一个承诺。比如说,你跟朋友约好了周末一起去看电影,这时候其实就有两种结果,要么到了周末你们真去看电影了(承诺实现),要么因为某些事儿看不成了(承诺失败)。

在代码里,Promise 也是用来处理异步操作的。异步操作就好比你在烧水的时候还能去干别的事儿,不用一直盯着水开没开。以前处理异步操作,代码会写得特别乱,嵌套很多层,Promise 就解决了这个问题。

下面给大家看个简单的 Promise 示例(技术栈:Javascript):

// 创建一个 Promise 实例
const myPromise = new Promise((resolve, reject) => {
    // 模拟一个异步操作,比如网络请求
    setTimeout(() => {
        const randomNumber = Math.random();
        if (randomNumber < 0.5) {
            // 如果随机数小于 0.5,认为操作成功,调用 resolve 函数
            resolve('操作成功');
        } else {
            // 如果随机数大于等于 0.5,认为操作失败,调用 reject 函数
            reject('操作失败');
        }
    }, 1000);
});

// 使用 then 方法处理 Promise 成功的情况,使用 catch 方法处理 Promise 失败的情况
myPromise.then((result) => {
    console.log(result);
}).catch((error) => {
    console.log(error);
});

在这个例子里,我们创建了一个 Promise 实例,用 setTimeout 模拟了一个异步操作。resolve 函数就相当于承诺实现了,reject 函数就相当于承诺失败了。然后用 then 方法处理成功的结果,用 catch 方法处理失败的结果。

二、Promise 的状态

Promise 有三种状态:

  1. pending(进行中):就像你跟朋友约好了看电影,但是还没到周末,这事儿还在进行中,结果还不确定。
  2. fulfilled(已成功):到了周末,你们顺利去看电影了,承诺实现了。
  3. rejected(已失败):到了周末,因为某些原因看不成电影了,承诺失败了。

一旦 Promise 的状态从 pending 变成了 fulfilled 或者 rejected,就不能再变了。就好比你跟朋友已经确定看不成电影了,就不能再反悔说又能看了。

下面这个示例(技术栈:Javascript),我们来看看状态的变化:

// 创建一个 Promise 实例
const promise = new Promise((resolve, reject) => {
    console.log('Promise 状态:pending');
    setTimeout(() => {
        // 模拟操作成功,改变状态为 fulfilled
        resolve('操作完成');
        console.log('Promise 状态:fulfilled');
    }, 1000);
});

// 处理 Promise 成功的情况
promise.then((result) => {
    console.log(result);
});

在这个例子里,一开始 Promise 状态是 pending,过了 1 秒后,调用 resolve 函数,状态变成了 fulfilled

三、Promise 的链式调用

Promise 的一个强大之处就是可以链式调用。就好比你完成了一件事儿之后,接着去做另一件事儿。

下面这个示例(技术栈:Javascript)展示了链式调用:

// 创建第一个 Promise 实例
const firstPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        // 模拟操作成功,传递结果 '第一步完成'
        resolve('第一步完成');
    }, 1000);
});

// 链式调用
firstPromise.then((result) => {
    console.log(result);
    // 返回一个新的 Promise 实例
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 模拟操作成功,传递结果 '第二步完成'
            resolve('第二步完成');
        }, 1000);
    });
}).then((newResult) => {
    console.log(newResult);
});

在这个例子里,第一个 Promise 完成后,返回了一个新的 Promise,然后接着处理这个新的 Promise。这样就可以把多个异步操作按顺序连起来,避免了回调地狱。

四、手写一个符合规范的 Promise

现在我们来自己动手写一个简单的 Promise。

1. 基本结构

首先,我们要创建一个 MyPromise 类,它有三种状态,还有 resolvereject 方法。

// 定义 MyPromise 类
class MyPromise {
    // 定义三种状态
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(executor) {
        // 初始状态为 pending
        this.status = MyPromise.PENDING;
        // 存储成功的结果
        this.value = null;
        // 存储失败的原因
        this.reason = null;
        // 存储成功的回调函数
        this.onFulfilledCallbacks = [];
        // 存储失败的回调函数
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === MyPromise.PENDING) {
                // 如果状态是 pending,将状态改为 fulfilled
                this.status = MyPromise.FULFILLED;
                this.value = value;
                // 执行所有成功的回调函数
                this.onFulfilledCallbacks.forEach((callback) => callback());
            }
        };

        const reject = (reason) => {
            if (this.status === MyPromise.PENDING) {
                // 如果状态是 pending,将状态改为 rejected
                this.status = MyPromise.REJECTED;
                this.reason = reason;
                // 执行所有失败的回调函数
                this.onRejectedCallbacks.forEach((callback) => callback());
            }
        };

        try {
            // 执行 executor 函数,传入 resolve 和 reject 函数
            executor(resolve, reject);
        } catch (error) {
            // 如果执行过程中出错,调用 reject 函数
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 如果 onFulfilled 不是函数,将其替换为一个返回 value 的函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
        // 如果 onRejected 不是函数,将其替换为一个抛出 reason 的函数
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };

        const newPromise = new MyPromise((resolve, reject) => {
            const handleFulfilled = () => {
                try {
                    // 执行 onFulfilled 函数,获取结果
                    const result = onFulfilled(this.value);
                    // 处理结果
                    resolve(result);
                } catch (error) {
                    // 如果执行过程中出错,调用 reject 函数
                    reject(error);
                }
            };

            const handleRejected = () => {
                try {
                    // 执行 onRejected 函数,获取结果
                    const result = onRejected(this.reason);
                    // 处理结果
                    resolve(result);
                } catch (error) {
                    // 如果执行过程中出错,调用 reject 函数
                    reject(error);
                }
            };

            if (this.status === MyPromise.FULFILLED) {
                // 如果状态是 fulfilled,立即执行 handleFulfilled 函数
                setTimeout(handleFulfilled, 0);
            } else if (this.status === MyPromise.REJECTED) {
                // 如果状态是 rejected,立即执行 handleRejected 函数
                setTimeout(handleRejected, 0);
            } else if (this.status === MyPromise.PENDING) {
                // 如果状态是 pending,将 handleFulfilled 和 handleRejected 函数存储起来
                this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
                this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
            }
        });

        return newPromise;
    }
}

// 使用自定义的 MyPromise
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功');
    }, 1000);
});

promise.then((result) => {
    console.log(result);
    return '新的结果';
}).then((newResult) => {
    console.log(newResult);
});

2. 代码解释

  • 状态管理:在 constructor 里,我们把 Promise 的初始状态设为 pending,还有 resolvereject 方法来改变状态。
  • then 方法then 方法用来处理 Promise 的结果。它会返回一个新的 Promise,这样就能实现链式调用。

五、应用场景

Promise 在很多场景都特别有用:

1. 网络请求

在前端开发中,经常要从服务器获取数据,这时候就可以用 Promise 来处理网络请求。

// 封装一个简单的网络请求函数,返回一个 Promise 实例
function fetchData() {
    return new Promise((resolve, reject) => {
        // 模拟网络请求
        setTimeout(() => {
            const data = { message: '这是从服务器获取的数据' };
            // 模拟操作成功,传递数据
            resolve(data);
            // 可以模拟操作失败,调用 reject 函数
            // reject('请求失败');
        }, 1000);
    });
}

// 使用封装的网络请求函数
fetchData().then((result) => {
    console.log(result.message);
}).catch((error) => {
    console.log(error);
});

2. 文件读取

在 Node.js 里,读取文件也是异步操作,用 Promise 可以让代码更清晰。

const fs = require('fs');

// 封装一个读取文件的函数,返回一个 Promise 实例
function readFilePromise(filePath) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf8', (error, data) => {
            if (error) {
                // 如果读取文件出错,调用 reject 函数
                reject(error);
            } else {
                // 如果读取文件成功,传递数据
                resolve(data);
            }
        });
    });
}

// 使用封装的读取文件函数
readFilePromise('test.txt').then((data) => {
    console.log(data);
}).catch((error) => {
    console.log(error);
});

六、技术优缺点

优点

  • 避免回调地狱:前面也提到了,Promise 的链式调用让代码更清晰,不会像以前那样嵌套很多层回调函数。
  • 状态管理:Promise 的状态一旦确定就不能再变,这样能避免很多错误。

缺点

  • 无法取消:Promise 一旦创建就不能取消,不像有些异步操作可以中途停止。
  • 错误处理复杂:在链式调用里,如果中间某个环节出错,错误处理可能会比较复杂。

七、注意事项

  • then 方法的返回值then 方法一定要返回一个新的 Promise,这样才能实现链式调用。
  • 错误处理:在链式调用里,最好在最后加上 catch 方法,这样能捕获前面所有的错误。

八、文章总结

通过这篇文章,我们了解了 Promise 的基本概念、状态、链式调用,还自己动手实现了一个简单的 Promise。Promise 是 JavaScript 里处理异步操作的重要工具,能让代码更清晰、更易维护。虽然它有一些缺点,但在很多场景下还是非常有用的。在实际开发中,我们要合理使用 Promise,注意它的注意事项,这样才能发挥它的最大作用。