欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一篇文章搞懂Promise

程序员文章站 2024-02-04 08:13:22
...

一.什么是Promise

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

二.Promise的特性

  • promise对象代表一个异步操作,有三种状态:pending、fulfilled、reject.

  • 任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变它的状态.

三.Promise的出现解决了什么问题

promise是其实就是一个异步编程的方案,在promise出现之前经常能出现所谓的"回调地狱"的嵌套代码:

step(1, function() {
    step(2, function() {
        step(3, function() {
            ……….
        })
    })
})
复制代码

都知道这种"回调地狱",是一种很让人恼火的代码书写格式,嵌套多层之后会让人阅读起来怀疑人生的。但是这种"回调地狱"真的就只有格式的问题嘛?显然不是。

一个来自《YDKJS》的例子:一个程序员开发了一个付款的系统,它良好的运行了很长时间。突然有一天,一个客户在付款的时候信用卡被连续刷了五次。这名程序员在调查了以后发现,一个第三方的工具库因为某些原因把付款回调执行了五次。在与第三方团队沟通之后问题得到了解决。

上面的例子就是一个 信任问题 回调函数的方式会引发很多信任问题,例如重复调用,调用太晚等等问题。那么promise怎么解决上述两个问题的呢?

1.可读性

首先是promise的then方法,支持我们把"回调"写在then方法中,让人阅读起来就像看同步代码一样享受.下面是用promise改写之后的代码:

Promise.resolve(1)
    .then(step => ++step)
    .then(step => ++step)
    .then(step => ++step)
    …
复制代码

2.信任问题

这种机制有点像事件的触发。它与普通的回调的方式的区别在于,普通的方式,回调成功之后的操作直接写在了回调函数里面,而这些操作的调用由第三方控制。在Promise的方式中,回调只负责成功之后的通知,而回调成功之后的操作放在了then的回调里面,由Promise精确控制。

Promise的状态不可逆改变,一旦发生改变就没有办法再改变成任何一种状态。Promise的特征保证了Promise可以解决信任问题。

对于回调过早的问题,由于Promise只能是异步的,所以不会出现异步的同步调用。即便是在异步操作之前的错误,也是异步的,并不是会产生同步(调用过早)的错误。

四.实现promise要注意的点

1.符合promise/A+规范

在promise/A+规范里面规范了promise中的状态类型,then方法的标准(约束条件)等,现在我们所用的所有promise库虽然有很多的扩展功能,但是都是基于promise/A+规范来实现的基本功能。

2.Promise的状态只能修改一次

promise的一个特性就是promise的状态只能从pending变成fulfilled,或者从pending变成rejected。并且这个状态是不可逆的,一旦状态发生改变,无论进行什么操作,这个promise实例的状态都不可更改,这也保证了使用promise的安全性。

3.then和catch需要异步调用

const p = new Promise((resolve, reject) => {
    resolve(1);
});
p.then((value) => {
    console.log(value);
});
console.log('hello world');

// output: hello world   1
复制代码

可以看到上述代码先执行的是hello world然后打印的才是1。

这就是小标题所说then和catch是需要等到同步代码执行完且promise发生改变之后才能执行的,也就是异步执行的。

4.then可以链式调用且返回一个新的promise

const p = new Promise((resolve, reject) => {
    resolve(1);
});
p.then((value) => {
    return value + 1;
})
.then(value => {
    console.log(value);
});

// output: 2
复制代码

上述代码是一个经典的promise的then方法的链式调用,每一个then方法都会返回一个新的promise,来调用一个新的then方法,从而实现链式调用。并且then方法返回的promise是一个新的promise,这个promise的值就是上一个then方法return出来的值。

5.then方法可以实现穿透效果

const p = new Promise((resolve, reject) => {
    resolve(1)
});
p.then()
.then((value) => {
    console.log(value);
});

// output: 1
复制代码

我们可以看到 promise实例p调用的第一个then方法并没有注册任何的回调函数,这个时候第二个then方法依然能够拿到value=1.这就是then方法的穿透效果,当我们给then方法传递的参数不是一个有效的函数的时候,promise会自动实现一个穿透的效果,确保下一个then方法可以正常调用。

五、写一个promise

const PENDING = 0; // 准备状态
const FULFILLED = 1; // 成功状态
const REJECTED = 2; // 失败状态

class Promise {
    constructor(fn) {
        this._state = PENDING;
        this._data = undefined;
        
        // 用来储存成功回调函数的数组
        this._onFulfilledCallback = [];
        // 用来储存失败回调函数的数组
        this._onRejectedCallback = [];

        // 当new一个promise实例的时候,立刻执行声明promise时候传递的回调函数
        run(this, fn);
    }

    then(onFulfilled, onRejected) {
    
        // 解决then方法的穿透问题,当传递的回调函数不是一个有效的函数的时候,
        // 我们需要手动把成功函数的回调和失败函数的回调重新赋值。
        if (typeof onFulfilled !== 'function') {
            onFulfilled = data => data;
        }
        if (typeof onRejected !== 'function') {
            onRejected = reason => { throw reason; }
        }
        
        // 定义一个新的promise供then方法返回
        let promise2;

        //当定义then方法的时候,promise是准备状态
        if (this._state === PENDING) {
            promise2 = new Promise((resolve, reject) => {
            // 当promise是准备状态的时候,把then方法的成功回调push进成功回调数组里面
                this._onFulfilledCallback.push((data) => {
                    try {
                        const x = onFulfilled(data);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            // 当promise是准备状态的时候,把then方法的失败回调push进失败回调数组里面
                this._onRejectedCallback.push((reason) => {
                    try {
                        const x = onRejected(reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        }

        //当定义then方法的时候,promise是失败状态
        if (this._state === REJECTED) {
            promise2 = new Promise((resolve, reject) => {
            
            // 根据promise/A+规范,then方法中的onFulfilled和onRejected方法需要
            // 在只包含平台代码中的执行栈中执行(可以简单理解为这两个方法是异步执行的)
            // 这里我们用setTimeout来模拟实现。
                setTimeout(() => {
                    try {
                        // 执行成功回调
                        const x = onRejected(this._data);
                        // 处理回调函数的返回值
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        // 如果代码报错,直接reject掉这个promise,不让报错信息外泄
                        reject(e);
                    }
                });
            });
        }

        // 当定义then方法的时候,promise是成功状态
        // 详细解释和成功状态同理
        if (this._state === FULFILLED) {
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this._data);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        }


        // 返回新的promise
        return promise2;
    }

    catch(onRejected) {
        // catch方法其实就是一个变形的then方法
        // 所以我们直接调用当前promise的then方法,将成功回调置空,失败回调
        // 直接使用catch方法传递进来的回调
        return this.then(undefined, onRejected);
    }
}

Promise.reject = (reason) => {
    // 返回一个成功状态的promise
    return new Promise((resolve, reject) => {
        reject(reason);
    });
}
Promise.resolve = (data) => {
    // 返回一个失败状态的promise
    return new Promise((resolve, reject) => {
        resolve(data);
    });
}


// 兼容deferred方法,跑promiseA+规范case的时候需要这个方法
Promise.deferred = () => {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });

    return dfd;
}


// 处理onFulfilled和onRejected方法的返回值
const resolvePromise = (promise2, x, resolve, reject) => {
    if (promise2 === x) {  // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错
        return reject(new TypeError('循环引用'));
    }

    let called = false; // 避免多次调用
    // 如果x是一个promise对象 (该判断和下面 判断是不是thenable对象重复 所以可有可无)
    if (x instanceof Promise) { // 获得它的终值 继续resolve
        if (x._state === PENDING) { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject);
            }, reason => {
                reject(reason);
            });
        } else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise
            x.then(resolve, reject);
        }
        // 如果 x 为对象或者函数
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是否是thenable对象(具有then方法的对象/函数)
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, reason => {
                    if (called) return;
                    called = true;
                    reject(reason);
                })
            } else { // 说明是一个普通对象/函数
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

const resolve = (promise, data) => {
    // 当resolve的是一个promise的时候
    if (data instanceof Promise) {
        // 直接调用这个promise的then方法,然后用这个promise的then方法,
        // 来resolve或reject我当前promise的状态和值
        return data.then(
            d => resolve(promise, d),
            r => reject(promise, r)
        )
    }

    // promise状态的改变是异步的,这里给其用setTimeout进行包裹
    setTimeout(() => {
        // 当promise的状态已经不是准备状态时,不进行任何操作,确保状态不可变
        if (promise._state !== PENDING) {
            return;
        }
        promise._state = FULFILLED;
        promise._data = data;

        // reolve之后遍历成功回调数组中的方法,逐一执行
        for (let callback of promise._onFulfilledCallback) {
            callback(data);
        }
    });

}

// 详细解释同resolve
const reject = (promise, reason) => {
    setTimeout(() => {
        if (promise._state !== PENDING) {
            return;
        }
        promise._state = REJECTED;
        promise._data = reason;

        for (let callback of promise._onRejectedCallback) {
            callback(reason);
        }
    });
}

// 执行声明promise实例时注册的回调函数
const run = (promise, fn) => {
    try {
        // 给方法传递两个参数,这两个函数也就是我们在外部使用的reolve和reject
        fn(
            data => resolve(promise, data),
            reason => reject(promise, reason)
        );
    } catch (e) {
        reject(e)
    };
}

// race方法,返回一个promise,这个promise的值是传递的promise数组中第一个发生状态改变的
// promise的值,这里需要注意,一旦有一个promise状态改变了,这个promise就会resolve
// 或者reject
Promise.race = (promises) => {
    return new Promise((resolve, reject) => {
        promises.forEach(promise => {
            promise.then(resolve, reject);
        });
    });
}

// all方法会返回一个promie,当传递的promise数组中的promise全都是成功状态 
// 的时候,返回的promise的值是一个所有传递的promise的值。当其中有一个promise是
// 失败状态的时候直接reject这个值
Promise.all = (promises) => {
    return new Promise((resolve, reject) => {
        const length = promises.length;
        const done = gen(length, resolve);
        promises.forEach((promise, index) => {
            promise.then((value) => {
                done(value, index);
            }, reject)
        });
    });
}
const gen = (length, resolve) => {
    let count = 0;
    const result = [];
    return function (value, index) {
        result[index] = value;
        if (++count === length) {
            resolve(result);
        }
    }
}

module.exports = Promise;
复制代码

六、总结

当我们自己尝试模拟一次promise的时候,就会发现能更加深刻的理解promise,当需要分析代码执行顺序的时候,就能更多的从源码出发,而不是凭借感觉来判断输出。

转载于:https://juejin.im/post/5d41c58ef265da03925a247e