原生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 史洲宇 閱讀(...) 評論(...) 編輯 收藏
ag二分彩