前端基础(十):promise

字数:2874

阅读时间:10分钟

前言

promise是异步调用的解决方案,它有业界公认的标准:www.ituring.com.cn/article/665… 有许多优秀的实现插件,如:Jquery、Q等,ES6中也添加了该特性。

它的使用方式也很简单,如下代码:

let promise = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve('success');
    },1000);
});

promise.then(function(response){
   console.log(response); 
});

//也可以使用defer语法糖,省略创建Promise的过程
let defer = Promise.defer();
setTimeout(function(){
    defer.resolve('success');
},1000);

defer.promise.then(function(response){
   console.log(response); 
})
复制代码

但是如果我们想知道更多的内容,如:

1.promise中的执行器参数什么时候执行的。

2.then函数中的回调函数什么时候执行。

3.链式调用then函数时,它的执行顺序是什么样的;如果有一个promise被拒绝了,后续如何调用;如果代码出现异常,后续then函数如何调用;如果上一个回调函数返回了一个promise对象,又是如何运行的...

这时,最好的办法莫过于查看实现源码了,但更好的办法是我们自己去实现一次。因此,本文我会与大伙一起基于A+规范实现一个自定义的Promise,旨在更好地理解promise的用法。

正文

先上标准,

中文:www.ituring.com.cn/article/665…

英文:promisesaplus.com/

一、实现一个promise类

基于上述标准,理清思路:

1.我们需要实现一个Promise类,其构造函数接受一个执行器参数,并且会在构造函数中调用该执行器。

2.Promise应有当前状态、终值、原因三个属性。

3.Promise应该实现执行、拒绝两个动作。

4.由于then是可以多次调用的,所以,Promise中应该有存储执行动作和拒绝动作的两个队列。

5.Promise应该暴露一个then函数,用来处理回调函数。

按照上述思路,我们编写出如下代码:

/**
 * 自定义promise对象
 */
class Promise {
    /**
     * 构造函数
     * @param  {Function} excutor 执行器
     */
    constructor (excutor) {
        //promise状态,有pending、resolved、rejected
        this.status = 'pending';

        //终值
        this.value;

        //拒因
        this.reason;

        //解决函数队列
        this.resolveFuns = [];

        //拒绝函数队列
        this.rejectFuns = [];

        //解决函数
        let resolve = val => {
            if (this.status === 'pending') {
                this.status = 'resolved';
                this.value = val;
                this.resolveFuns.forEach(func => func());
            }
        };

        //拒绝函数
        let reject = reason => {
            if (this.status === 'pending') {
                this.status = 'rejected';
                this.reason = reason;
                this.rejectFuns.forEach(func => func());
            }
        };

        try {
            excutor(resolve, reject);
        } catch (ex) {
            reject(ex);
        }
    }

    /**
     * 回调函数处理
     * @param  {Function} resolveCallBack 执行回调函数
     * @param  {Function} rejectCallBack  拒绝回调函数
     */
    then (resolveCallBack, rejectCallBack) {
        
    }
}
复制代码

参照规范:

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)执行态(Fulfilled)拒绝态(Rejected)

这里我们status属性对应如上三种状态(我们更习惯使用resolved来表示执行,因此没有使用fulfilled)。

构造函数中,resolve和reject函数的实现中,遵循了规范中对状态变化的规定:

等待态(Pending)

处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled)

处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)

处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

如果执行器函数执行报错,则直接执行拒绝动作。因此,如果我们使用promise时,传入的执行器执行报错,则会在then函数中的拒绝回调函数中捕获到错误。但是,如果是执行器函数中的延时操作报错(例setTimeout中报错),这里是无法捕获到错误的。

二、实现then函数

基于标准,理清思路:

1.then函数接受两个参数:执行回调函数和拒绝回调函数。如果传入的参数并非函数,则忽略它并将信息传递到下一个then函数中。

2.then函数必须返回一个新的promise。

3.如果promise状态为pending,则应该将回调函数推入队列;如果状态为resolved,则应直接调用执行回调函数;如果状态为reject,则应直接调用拒绝回调函数。如果回调函数执行报错,则应该执行下一个then函数中的决绝回调函数。

4.需要处理then的链式调用。

5.所有的回调函数都应该异步执行(为了保证then函数执行顺序的一致性。)

首先,我们编写一个对应参数和返回值的函数,代码如下:

	/**
     * 回调函数处理
     * @param  {Function} resolveCallBack 执行回调函数
     * @param  {Function} rejectCallBack  拒绝回调函数
     * @return {promise}                 新创建的promise
     */
    then (resolveCallBack, rejectCallBack) {
        let pDeffer = Promise.defer();
        if (this.status === 'pending') {
            //如果promise状态为pending则将回调函数加入队列中
            
        } else if (this.status === 'resolved') {
            //如果promise状态为resolved,则立即执行resolveCallBack回调函数,
            //如此则无论promise是否已经执行完毕,回调函数都必然会执行
            
        } else if (this.status === 'rejected') {
            //如果promise状态为rejected,则立即执行rejectedCallBack回调函数,
            //原因同上
           	
        }

        return pDeffer.promise;
    }
复制代码

其中defer函数是我在Promise对象上创建的一个静态函数,是一个创建Promise对象的语法糖函数,其代码如下:

/**
 * 创建一个默认的promise(语法糖)
 * @return {Object} deffer对象
 */
Promise.defer = Promise.deferred = () => {
    let defer = {};
    defer.promise = new Promise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
};
复制代码

然后根据整理的思路1中,需要处理参数不为函数的情况。执行回调函数直接返回接受的返回值即可,拒绝回调函数抛出对应原因的异常即可(这里只有抛出异常,下一个then函数才会调用拒绝回调函数)。得到如下代码:

	then (resolveCallBack, rejectCallBack) {
        //如果resolveCallBack不是函数,则将值传递到下一个resolveCallBack函数中
        resolveCallBack = Promise.isFunction(resolveCallBack) ? resolveCallBack : x => x;

        //如果rejectCallBack不是函数,则将原因传递到下一个rejectCallBack函数中
        rejectCallBack = Promise.isFunction(rejectCallBack) ? rejectCallBack : reason => {
            throw reason;
        };
        ...
    }
复制代码

然后,我们需要处理回调函数逻辑。三种状态的处理逻辑大致一致,我们就以最为复杂的pendding状态为例。

当状态为pendding时,我们需要将执行函数和拒绝函数分别推入promise的执行函数队列和拒绝函数队列中。该函数必须异步执行,并且如果执行报错,则直接执行下一个then的拒绝回调函数。还有,我们需要考虑终值可能会是一个promise对象的情况,这种情况我们封装一个函数在第三步进行实现。得到如下代码:

	/**
     * 回调函数处理
     * @param  {Function} resolveCallBack 执行回调函数
     * @param  {Function} rejectCallBack  拒绝回调函数
     * @return {promise}                 新创建的promise
     */
    then (resolveCallBack, rejectCallBack) {
        //如果resolveCallBack不是函数,则将值传递到下一个resolveCallBack函数中
        resolveCallBack = Promise.isFunction(resolveCallBack) ? resolveCallBack : x => x;

        //如果rejectCallBack不是函数,则将原因传递到下一个rejectCallBack函数中
        rejectCallBack = Promise.isFunction(rejectCallBack) ? rejectCallBack : reason => {
            throw reason;
        };

        /**
         * 解决promise函数
         * @param  {promise} promise 待解决的promise
         * @param  {*} x       上一个promise的终值
         * @param  {Function} resolve promise的执行回调函数
         * @param  {Function} reject  promise的拒绝回调函数
         */
        function resolvePromise (promise, x, resolve, reject) {
            
        }

        let pDeffer = Promise.defer();
        if (this.status === 'pending') {
            //如果promise状态为pending则将回调函数加入队列中
            //添加解决函数队列
            this.resolveFuns.push(() => {
                setTimeout(() => {
                	try {
	                    let x = resolveCallBack(this.value);
	                    resolvePromise(pDeffer.promise, x, pDeffer.resolve, pDeffer.reject);
                	} catch (ex) {
                		pDeffer.reject(ex);
                	}
                }, 0);
            });

            //添加拒绝函数队列
            this.rejectFuns.push(() => {
                setTimeout(() => {
                	try {
	                    let x = rejectCallBack(this.reason);
	                    resolvePromise(pDeffer.promise, x, pDeffer.resolve, pDeffer.reject);
                	} catch (ex) {
                		pDeffer.reject(ex);
                	}
                }, 0);
            });
        } else if (this.status === 'resolved') {
            //如果promise状态为resolved,则立即执行resolveCallBack回调函数,
            //如此则无论promise是否已经执行完毕,回调函数都必然会执行
            setTimeout(() => {
            	try {
	                let x = resolveCallBack(this.value);
	                resolvePromise(pDeffer.promise, x, pDeffer.resolve, pDeffer.reject);
            	} catch (ex) {
            		pDeffer.reject(ex);
            	}
            }, 0);
        } else if (this.status === 'rejected') {
            //如果promise状态为rejected,则立即执行rejectedCallBack回调函数,
            //原因同上
            setTimeout(() => {
            	try {
	                let x = rejectCallBack(this.reason);
	                resolvePromise(pDeffer.promise, x, pDeffer.resolve, pDeffer.reject);
            	} catch (ex) {
            		pDeffer.reject(ex);
            	}
            }, 0);
        }

        return pDeffer.promise;
    }
复制代码

三、实现解决promise函数

这一步就是为了实现上述的resolvePromise函数。

整理思路如下:

1.如果终值x与promise是同一个引用,则抛出异常(死循环)。

2.如果x是具有then函数的函数或对象,则递归调用。由于x是外部不可控变量,所以我们要保证只执行一次执行回调函数或者拒绝回调函数。

3.如果x并非具有then函数的函数或对象,则直接调用执行回调函数。

4.如果其中执行报错,则调用拒绝回调函数。

根据思路,编写出如下代码:

/**
* 解决promise函数
* @param  {promise} promise 待解决的promise
* @param  {*} x       上一个promise的终值
* @param  {Function} resolve promise的执行回调函数
* @param  {Function} reject  promise的拒绝回调函数
*/
function resolvePromise (promise, x, resolve, reject) {
    if (x === promise) {
        reject(new TypeError('终值与Promise相等,陷入死循环!'));
        return;
    }
    let bCalled = false;
    if (x != null && (Promise.isFunction(x) || Promise.isObject(x))) {
        try {
            let then = x.then;
            if (Promise.isFunction(then)) {
                then.call(x, y => {
                    if (bCalled === true) {
                        return;
                    }
                    bCalled = true;
                    resolvePromise(promise, y, resolve, reject);
                }, r => {
                    if (bCalled === true) {
                        return;
                    }
                    bCalled = true;
                    reject(r);
                });
            } else {
                if (bCalled === true) {
                    return;
                }
                bCalled = true;
                resolve(x);
            }
        } catch (ex) {
            if (bCalled === true) {
                return;
            }
            bCalled = true;
            reject(ex);
        }
    } else {
        if (bCalled === true) {
            return;
        }
        bCalled = true;
        resolve(x);
    }
}
复制代码

这里有两个注意点:

1.声明一个变量接收x.then变量,是为了避免多次访问x.then属性导致其值在检索时发生改变。这个可能有点难以理解,我们看一下 promises-aplus-tests 中的测试用例源码就更容易理解了:

describe("`x` is an object with normal Object.prototype", function () {
    var numberOfTimesThenWasRetrieved = null;

    beforeEach(function () {
        numberOfTimesThenWasRetrieved = 0;
    });

    function xFactory() {
        return Object.create(Object.prototype, {
            then: {
                get: function () {
                    ++numberOfTimesThenWasRetrieved;
                    return function thenMethodForX(onFulfilled) {
                        onFulfilled();
                    };
                }
            }
        });
    }

    testPromiseResolution(xFactory, function (promise, done) {
        promise.then(function () {
            assert.strictEqual(numberOfTimesThenWasRetrieved, 1);
            done();
        });
    });
});
复制代码

如上述代码,每次我们调用then属性时,都会导致numberOfTimesThenWasRetrieved的值加1,从而发生一些不可预期的问题。

2.当x为promise对象时,手动调用x的then函数,然后将返回值作为终值传入promise中,就是为了处理代码执行顺序的问题。只有当上一then函数中的代码执行完毕,才会执行下一个then函数中的回调函数。因此,我们在使用链式调用时,上一then函数中执行回调函数中往往会返回一个promise对象,就是这个原理。

编码工作到此完毕。我们可以使用promises-aplus-tests插件跑一把看看结果:

完整代码地址:

github.com/iTrustGuid/…

欢迎关注我的微信公众号:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值