一篇文章搞懂Promise

一.什么是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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值