教你一步一步實現一個Promise


Promise我想現在大家都非常熟悉了,主要作用就是解決異步回調問題,這里簡單介紹下。

Promise規范是CommonJS規范之一,而Promise規范又分了好多種,比如 Promises/A、Promises/B、Promises/Kiss等等

有興趣的可以到這多了解一些 http://wiki.commonjs.org/wiki/Promises

現在比較流行的是Promise/A規范,人們對它的完善和擴展,逐漸形成了Promise/A+規范,A+已脫穎而出。

說到這里規范是什么,可以去這里了解下

http://promises-aplus.github.io/promises-spec/

http://hussion.me/2013/10/19/promises-a/

現在已有瀏覽器內置支持Promise,它的api語法可以在這里查看

http://www.html5rocks.com/zh/tutorials/es6/promises/#toc-api

可以看到它的api並不多,其實規范也不多,我覺的大致抓住幾個重要的點就夠了,

1、promise有三種狀態,等待(pending)、已完成(fulfilled)、已拒絕(rejected)

2、promise的狀態只能從“等待”轉到“完成”或者“拒絕”,不能逆向轉換,同時“完成”和“拒絕”也不能相互轉換

3、promise必須有一個then方法,而且要返回一個promise,供then的鏈式調用,也就是可thenable的

4、then接受倆個回調(成功與拒絕),在相應的狀態轉變時觸發,回調可返回promise,等待此promise被resolved后,繼續觸發then鏈

知道這幾個重要的特點,我們就可以參考瀏覽器內置的api來實現了,

我們可以不必太受規范約束,先按照自己的想法來就好了。

promise的使用大致如下

var promise = new Promise(function(resolve, reject) {
	setTimeout(function(){
		resolve('val')
	});
});

promise.then(onFulfilled,onRejected).then(onFulfilled,onRejected)

主要思路就是我們可以直接對返回的promise對象進行操作,比如then,傳入回調,

這里的函數並不會立即執行,而是加入隊列,等待未來的某個時間resolve時被觸發執行。

有了以上說明,就可以來實現了

首先定義三個狀態

var PENDING = undefined, FULFILLED = 1, REJECTED = 2;

然后實現Promise構造函數,此函數接受一個函數參數,函數參數接受倆個我們提供的方法resolve與reject,

供使用者在未來的某個時間里調用觸發執行我們的隊列,這里還要初始下當前的狀態,傳遞的值,

以及then時保存到的隊列。

大概像下面這樣

var Promise = function(resolver){
    if (!isFunction(resolver))
        throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
    if(!(this instanceof Promise)) return new Promise(resolver);

    var promise = this;
    promise._value;
    promise._reason;
    promise._status = PENDING;
    promise._resolves = [];
    promise._rejects = [];
    
    var resolve = function(value){
        //狀態轉換為FULFILLED 
        //執行then時保存到_resolves里的回調,
        //如果回調有返回值,更新當前_value
    }
    var reject = function(reason){
        //狀態轉換為REJECTED
        //執行then時保存到_rejects里的回調,
        //如果回調有返回值,更新當前_rejects
    }
    
    resolver(resolve,reject);
}

有了這個,我們在實現一個then就ok了,

then里要做的就是返回一個promise供then的鏈式調用,

而且promise.then(onFulfilled,onRejected)時,我們要判斷當前promise的狀態,

如果是pending則把onFulfilled與onRejected添加到_resolves與_rejects數組里,

否則的話根據狀態,直接觸發回調,這里要注意的是,如果返回的是promise,我們要等到此promise被resolves時,

觸發then鏈的下一個promise執行。

代碼大概是這樣

Promise.prototype.then = function(onFulfilled,onRejected){
    var promise = this;
    // 每次返回一個promise,保證是可thenable的
    return Promise(function(resolve,reject){
        function callback(value){
          var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
          if(isThenable(ret)){
              // 根據返回的promise執行的結果,觸發下一個promise相應的狀態
            ret.then(function(value){
               resolve(value); 
            },function(reason){
               reject(reason);
            });
          }else{
            resolve(ret);
          }
        }
        function errback(reason){
            reason = isFunction(onRejected) && onRejected(reason) || reason;
            reject(reason);
        }
        if(promise._status === PENDING){
               promise._resolves.push(callback);
               promise._rejects.push(errback);
           }else if(promise._status === FULFILLED){ // 狀態改變后的then操作,立刻執行
               callback(promise._value);
           }else if(promise._status === REJECTED){
               errback(promise._reason);
           }
    });
}

這里說明下

var isThenable = function(obj){
  return obj && typeof obj['then'] == 'function';
}

也就是說返回的對象帶有then方法,我們就當作promise對象

到這里我們主要的工作就完成了,其他的all,race等方法都很簡單,具體可以到這里看完整的實現

https://github.com/ygm125/promise/blob/master/promise.js

下面我們來做幾個例子來看下效果

var getData100 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve('100ms');
        },100);
    });
}

var getData200 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve('200ms');
        },200);
    });
}

getData100().then(function(data){
    console.log(data); // 100ms
    return getData200();
}).then(function(data){
    console.log(data); // 200ms
    return data + data;
}).then(function(data){
    console.log(data) // 200ms200ms
});

當然可以直接getData100().then(getData200).then(function(val){})

then可以只傳一個,接受成功的回調,也可以用catch方法,接受失敗的回調,

catch是then的一個語法糖,相當於promise.then(undefined, onRejected)

也可以用all來並行執行

Promise.all([getData100(),getData200()]).then(function(value){
    console.log(value) // ['100ms','200ms']
});

結果的順序與傳入的順序相同。

我們也可以直接創建一個以obj為值的成功狀態的promise,

Promise.resolve('FULFILLED').then(function(val){
    console.log(val) // FULFILLED
});

實現都相當簡單,看代碼就懂。

這里也可以做一些好玩的,比如創建一個delay方法

Promise.prototype.delay = function(ms){
    return this.then(function(val){
        return Promise.delay(ms,val);
    })
}

Promise.delay = function(ms,val){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(val);
        },ms);
    })
}

我們可以每隔多少毫秒執行一些操作

Promise.delay(1000).then(function(){
    // 一些操作
}).delay(1000).then(function(){
    // 一些操作
})

我們也可以包裝一個循環,執行多少次,每次延遲多少秒執行什么操作

 


var len = 0,
    words = '你好,你是誰?';

function count(num,ms,cb){
    var pro = Promise.resolve();
    for (var i = 0; i < num; i++) {
        pro = pro.delay(ms).then(function(v){
            return cb(v);
        });
    };
}

count(words.length,800,function(){
    var w = words.substr(0,++len);
    console.log(w);
})

更多的東西等你來實現~ 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM