原生js实现Promise

由于浏览器兼容性的限制,我们不得不通过原生js实现Promise方法。

原生的Promise对象包含promise,promiseAll,rase等方法,下面的代码基本上实现了这些方法,但在细微处可能有所区别,主要是为了方便项目使用才这么设计。

promise

promise方法接收一个函数作为参数(如果参数传错会进入异常捕获阶段),参数会被分裂成两个变量一个resolve, reject。改方法返回当前的promise的实例,可以实现链式操作。

具体使用如下

 mrChart.promise(function(resolve, reject){
        setTimeout(function(){
            resolve('3秒后弹出成功!')
        },3000)
        setTimeout(function () {
            reject('4秒后弹出失败!')
        }, 4000)
    }).then(function(a) {
        console.log(a[1])
        alert(a[1])
    },function(a){
        console.log(a[1])
        alert(a[1])
    })

关于then方法的使用是接收两个参数(都为函数),第一个参数对应于方法resolve,第二个参数对应于reject,如果不传或者传少了,代码会自动给你生成一个空函数,但是这个函数是捕获不到信息的。

 promise.all

promise.all方法是为了监听多个promise对象设计的,它接收多个promise作为参数,以实现多个等待的效果

假如我们创建三个promise

var a = mrChart.promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('3秒后弹出成功!')
        }, 3000)
    })
var b = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        reject('4秒后失败!')
    }, 4000)
})
var c = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('5秒后弹出成功!')
    }, 5000)
})

我们创建一个promise.all方法,监听上面多个promise的对象状态,只有所有的promise都会成功了才会进入到all方法的成功回调,否则会reject(失败)

代码如下:

var d = mrChart.promise.all(a,b,c).then(function(){
    console.log('全部都成功了',arguments)
}).catch(function(){
    console.log('其中有失败的', arguments);
})

 promise.rase

还有一个方法rase,字面上是奔跑的意思,我理解是单步rase,可以称之为管道的概念,只有其中一个失败或成功(这取决于谁先跑完),类似于看谁先完成就有谁来决定这次promise的状态,正常业务中用到的场景不是很多,这里也只是简单的实现一下,可能具体细节还实现的不好,有兴趣的可以在上面进行扩展。

这里我们看下代码使用的例子

//单步跑 rase
var a = mrChart.promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('3秒后弹出成功!')
        }, 3000)
    })
var b = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        reject('4秒后失败!')
    }, 2000)
})
var d = mrChart.promise.race(a,b).then(function(){
    console.log('谁快执行哪一个--成功的',arguments)
},function(){
    console.log('谁快执行哪一个--失败的', arguments)
}).catch(function(){
    console.log('单个异常会成为当前rase的异常');
})

 本插件中还用到发布订阅Emiter,通过这个来辅助完成promise的状态

Emiter实现如下:

//事件订阅区域
     function Emiter(){
        this._events = Object.create(null);
     }
     Emiter.prototype.on = function(event, fn){
         var vm = this;
         if (Array.isArray(event)) {
             for (var i = 0, l = event.length; i < l; i++) {
                 vm.on(event[i], fn);
             }
         } else {
             (vm._events[event] || (vm._events[event] = [])).push(fn);
         }
         return vm
     }
     Emiter.prototype.once = function (event, fn) {
          var vm = this;
         function on() {
             vm.off(event, on);
             fn.apply(vm, arguments);
         }
         on.fn = fn;
         vm.on(event, on);
         return vm
     }
     Emiter.prototype.off = function (event, fn) {
         var vm = this;
         // all
         if (!arguments.length) {
             vm._events = Object.create(null);
             return vm
         }
         // array of events
         if (Array.isArray(event)) {
             for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
                 vm.off(event[i$1], fn);
             }
             return vm
         }
         // specific event
         var cbs = vm._events[event];
         if (!cbs) {
             return vm
         }
         if (!fn) {
             vm._events[event] = null;
             return vm
         }
         // specific handler
         var cb;
         var i = cbs.length;
         while (i--) {
             cb = cbs[i];
             if (cb === fn || cb.fn === fn) {
                 cbs.splice(i, 1);
                 break
             }
         }
         return vm
     }
     Emiter.prototype.emit = function (event) {
         var vm = this;
         var cbs = vm._events[event];
         if (cbs) {
             cbs = cbs.length > 1 ? toArray(cbs) : cbs;
             var args = toArray(arguments, 1);
             for (var i = 0, l = cbs.length; i < l; i++) {
                 cbs[i].apply(vm,args);
             }
         }else{
             error('[mrChart error]:Chart:Emiter.emit event is not found');
         }
         return vm
     }
Emiter源码参考vue2.0的源码实现

下面是完整的代码
/*!
    * 针对图形化框架设计的promise 插件
    * @version   1.0.0
    * 
    */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
        typeof define === 'function' && define.amd ? define(['exports'], factory) :
            (factory((global.mrChart = global.mrChart || {})));
}(this, (function (exports) {
    'use strict'; 
    function toArray(list, start) {
        start = start || 0;
        var i = list.length - start;
        var ret = new Array(i);
        while (i--) {
            ret[i] = list[i + start];
        }
        return ret
    }
    function noop(a, b, c) { }

    var hasConsole = typeof console === 'object'

    function log() {
        if (hasConsole) {
            Function.apply.call(console.log, console, arguments)
        }
    }

    function warn() {
        if (hasConsole) {
            var method = console.warn || console.log
            // http://qiang106.iteye.com/blog/1721425
            Function.apply.call(method, console, arguments)
        }
    }

    function error(str, e) {
        throw (e || Error)(str)
    }


    function isObject(input) {
        // IE8 will treat undefined and null as object if it wasn't for
        // input != null
        return input != null && Object.prototype.toString.call(input) === '[object Object]';
    }

    function isFunction(input) {
        return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
    }

     //事件订阅区域
     function Emiter(){
        this._events = Object.create(null);
     }
     Emiter.prototype.on = function(event, fn){
         var vm = this;
         if (Array.isArray(event)) {
             for (var i = 0, l = event.length; i < l; i++) {
                 vm.on(event[i], fn);
             }
         } else {
             (vm._events[event] || (vm._events[event] = [])).push(fn);
         }
         return vm
     }
     Emiter.prototype.once = function (event, fn) {
          var vm = this;
         function on() {
             vm.off(event, on);
             fn.apply(vm, arguments);
         }
         on.fn = fn;
         vm.on(event, on);
         return vm
     }
     Emiter.prototype.off = function (event, fn) {
         var vm = this;
         // all
         if (!arguments.length) {
             vm._events = Object.create(null);
             return vm
         }
         // array of events
         if (Array.isArray(event)) {
             for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
                 vm.off(event[i$1], fn);
             }
             return vm
         }
         // specific event
         var cbs = vm._events[event];
         if (!cbs) {
             return vm
         }
         if (!fn) {
             vm._events[event] = null;
             return vm
         }
         // specific handler
         var cb;
         var i = cbs.length;
         while (i--) {
             cb = cbs[i];
             if (cb === fn || cb.fn === fn) {
                 cbs.splice(i, 1);
                 break
             }
         }
         return vm
     }
     Emiter.prototype.emit = function (event) {
         var vm = this;
         var cbs = vm._events[event];
         if (cbs) {
             cbs = cbs.length > 1 ? toArray(cbs) : cbs;
             var args = toArray(arguments, 1);
             for (var i = 0, l = cbs.length; i < l; i++) {
                 cbs[i].apply(vm,args);
             }
         }else{
             error('[mrChart error]:Chart:Emiter.emit event is not found');
         }
         return vm
     }
     
    var pN = 'MrPromise';

    function isPromise(n){
        return n.isPromise || false
    }

    function promiseResolve(){
        var promise = this;
        return function(){
            try {
                promise.state = 'resolve';
                promise.callbacks[ 0 ]([arguments, 'resolve']);
                promise.data = arguments;
                if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [arguments, promise.state])
            } catch (e) {
                promise.state = 'catch';
                promise.callbacks[ 2 ]([e, 'catch'])
                promise.data = e;
                if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [e, promise.state])
            }
            return promise;
        }
    }

    function promiseReject() {
        var promise = this;
        return function () {
            try {
                promise.state = 'reject';
                promise.callbacks[ 1 ]([arguments, 'reject'])
                promise.data = arguments;
                if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [arguments, promise.state])
            } catch (e) {
                promise.state = 'catch';
                promise.callbacks[ 2 ]([e, 'catch'])
                promise.data = e;
                if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [e, promise.state])
            }
            return promise;
        }
    }

    function promiseResolveAll(){
        var promise = this;
        return function () {
            promise.state = 'resolve';
            promise.callbacks[0]([arguments, 'resolve']);
            return promise;
        }
    }

    function promiseRejectAll() {
        var promise = this;
        return function () {
            promise.state = 'catch';
            promise.callbacks[1]([arguments, 'catch'])
            return promise;
        }
    }

    function promiseResolveRace() {
        var promise = this;
        return function () {
            promise.state = 'resolve';
            promise.callbacks[0]([arguments, 'resolve']);
            return promise;
        }
    }

    function promiseRejectRace() {
        var promise = this;
        return function () {
            promise.state = 'reject';
            promise.callbacks[1]([arguments, 'reject'])
            return promise;
        }
    }

    function promiseAll(dependent){
        // 初始promise状态
        this.state = 'pending';
        this.isPromise = true;
        this.dependent = dependent;
        // resolve catch==reject
        this.callbacks = [noop, noop];
        this.resolve = promiseResolveAll.call(this),
        this.reject = promiseRejectAll.call(this);
        this.Emit = new Emiter();
        this.emitName = 'ResolveState';
        for (var i = 0; i < dependent.length;i++){
            dependent[i].bindEmit(this.Emit,'ResolveState');
        }
        var promise = this;
        var reason = [new Array(dependent.length), new Array(1)];
        this.Emit.on(this.emitName,function(){
            var n = 0,m = 0;
            for (var i = 0; i < promise.dependent.length;i++){
                var state = promise.dependent[i].state
                if (state === 'resolve'){
                    n++;
                    reason[0][i] = promise.dependent[i].data;
                } else if (state !== 'pending'){
                    reason[0] = new Array(promise.dependent.length)
                    reason[1][0] = promise.dependent[i].data;
                }
                if (state !== 'pending') m++
            }
            if (n === promise.dependent.length){
                promise.resolve(reason[0])
                reason = [new Array(dependent.length), new Array(1)];
            } else if (m === promise.dependent.length){
                promise.reject(reason[1])
                reason = [new Array(dependent.length), new Array(1)];
            }
        });
        return this;
    }

    promiseAll.prototype = {
        constructor: promiseAll,
        then:function(){
            var args = arguments;
            if (isFunction(args[0])) this.callbacks[0] = args[0]
            return this;
        },
        catch:function(){
            var args = arguments;
            if (isFunction(args[0])) this.callbacks[1] = args[0]
            return this;
        }
    }

    function MrPromise(fn) {
        // 初始promise状态
        this.state = 'pending';
        this.isPromise = true;
        // resolve reject catch
        this.callbacks = [noop,noop,noop];
        this.reject = 
        this.resolve = noop;
        this.Emit = {
            emit: noop
        };
        this.data = '';
        this.emitName = '';
        this.resolve = promiseResolve.call(this), 
        this.reject = promiseReject.call(this);
        fn(this.resolve, this.reject);
        return this;
    }

    function PromiseRace(dependent){
        // 初始promise状态
        this.state = 'pending';
        this.isPromise = true;
        this.dependent = dependent;
        // resolve reject catch
        this.callbacks = [noop, noop, noop];
        this.resolve = promiseResolveRace.call(this),
        this.reject = promiseRejectRace.call(this);
        this.Emit = new Emiter();
        this.emitName = 'ResolveStateRace';
        for (var i = 0; i < dependent.length; i++) {
            dependent[i].bindEmit(this.Emit, 'ResolveStateRace');
        }
        var promise = this;
        var reason = [new Array(1), new Array(1)];
        this.Emit.on(this.emitName, function (data) {
            if(promise.state !== 'pending') return;
            if (data[1] === 'resolve'){
                promise.resolve(data[0])
            }
            if (data[1] === 'reject') {
                promise.reject(data[0])
            }
            if (data[1] === 'catch') {
                promise.state = 'catch';
                promise.callbacks[2](data[0])
            }
        });
        return this;
    }

    PromiseRace.prototype = {
        constructor: PromiseRace,
        then: function () {
            var args = arguments;
            if (isFunction(args[0])) this.callbacks[0] = args[0]
            if (isFunction(args[1])) this.callbacks[1] = args[1]
            return this;
        },
        catch: function () {
            var args = arguments;
            if (isFunction(args[0])) this.callbacks[2] = args[0]
            return this;
        }
    }

    MrPromise.promise = function(){
        var args = arguments,
            l = args.length,
            fn = args[0];
        if (!l) error(pN + '() 请传入一个函数');
        if (l > 1) warn(pN + '() 参数长度为1');
        if (!isFunction(fn)) error(pN + '() 参数类型不是一个函数');
        return new MrPromise(fn)
    }

    //单步跑promise
    MrPromise.promise.race = function(){
        var dependent = []; //依赖promise
        var args = arguments,
            isAllPromise = false,
            n = 0,
            l = args.length;
        if (!l) error('promiseRace() 参数长度至少1')
        for (var i = 0; i < args.length; i++) {
            if (isPromise(args[i])) {
                n++
                dependent.push(args[i])
            }
        }
        if (n == l) isAllPromise = true
        if (!isAllPromise) error('promiseRace() 参数必须为promise对象')
        return new PromiseRace(dependent);
    }

    MrPromise.promise.all = function(){
        var dependent = []; //依赖promise
        var args = arguments,
            isAllPromise = false,
            n = 0,
            l = args.length;
        if (!l) error('promiseAll() 参数长度至少1')
        for (var i = 0; i < args.length; i++) {
            if (isPromise(args[i])) {
                n++
                dependent.push(args[i])
            }
        }
        if (n == l) isAllPromise = true
        if (!isAllPromise) error('promiseAll() 参数必须为promise对象')
        return new promiseAll(dependent);
    }

    

    MrPromise.prototype = {
        constructor: MrPromise,
        then:function(){
            var args = arguments;
            if (isFunction(args[ 0 ])) this.callbacks[ 0 ] = args[ 0 ]
            if (isFunction(args[ 1 ])) this.callbacks[ 1 ] = args[ 1 ]
            return this;
        },

        catch:function(){
            var args = arguments;
            if (isFunction(args[0])) this.callbacks[ 2 ] = args[0]
            return this;
        },

        bindEmit:function(emit,name){
            this.Emit = emit;
            this.emitName = name;
            return this;
        }
    }

    exports.promise = MrPromise.promise;
    Object.defineProperty(exports, '__esModule', { value: true });

})));

下面是完整使用的demo代码

/*测试promise*/
/*
    mrChart.promise(function(resolve, reject){
        setTimeout(function(){
            resolve('3秒后弹出成功!')
        },3000)
        setTimeout(function () {
            reject('4秒后弹出失败!')
        }, 4000)
    }).then(function(a) {
        console.log(a[1])
        alert(a[1])
    },function(a){
        console.log(a[1])
        alert(a[1])
    })
*/
/*
    var a = mrChart.promise(function(resolve, reject){
        setTimeout(function () {
            resolve('3秒后弹出成功!')
        }, 3000)
        setTimeout(function() {
            reject('4秒后弹出成功!')
        }, 4000);
    })
    .then(function(){
        return c(); //这里故意写错 导致异常抛出
    },function(){
        console.log(11111)
    })
    .catch(function(){
        //会进入这里进行捕获
        console.log(arguments)
    })
*/
//var c = mrChart.promise('1',2); 异常捕获测试
//var c = mrChart.promise(); 异常捕获测试
/*
//promise.all 测试
var a = mrChart.promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('3秒后弹出成功!')
        }, 3000)
    })
var b = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        reject('4秒后失败!')
    }, 4000)
})
var c = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('5秒后弹出成功!')
    }, 5000)
})
var d = mrChart.promise.all(a,b,c).then(function(){
    console.log('全部都成功了',arguments)
}).catch(function(){
    console.log('其中有失败的', arguments);
})
*/
/*
//单步跑 rase
var a = mrChart.promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('3秒后弹出成功!')
        }, 3000)
    })
var b = mrChart.promise(function (resolve, reject) {
    setTimeout(function () {
        reject('4秒后失败!')
    }, 2000)
})
var d = mrChart.promise.race(a,b).then(function(){
    console.log('谁快执行哪一个--成功的',arguments)
},function(){
    console.log('谁快执行哪一个--失败的', arguments)
}).catch(function(){
    console.log('单个异常会成为当前rase的异常');
})
*/

以上只是简单实现了promise的部分功能,更复杂的功能我这里用不到也就没实现,基本上日常使用够了!

posted @ 2019-10-14 10:34  史洲宇  阅读(1962)  评论(0编辑  收藏  举报