日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

es5如何实现promise_彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)...

發布時間:2023/12/20 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 es5如何实现promise_彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

眾所周知javascript語言的一大特色就是異步,這既是它的優點,同時在某些情況下也帶來了一些的問題。最大的問題之一,就是異步操作過多的時候,代碼內會充斥著眾多回調函數,乃至形成回調金字塔。為了解決回調函數帶來的問題,Promise作為一種更優雅的異步解決方案被提出,最初只是一種實現接口規范,而到了es6,則是在語言層面就原生支持了Promise對象。

最初接觸Promise的時候,我覺得它是比較抽象并且令人困惑的,相信很多人也有同樣的感覺。但是在后來的熟悉過程中,我慢慢體會到了它的優雅,并開始思考Promise對象實現的原理,最終用es5語法實現了一個具備基本功能的自己的Promise對象。在這篇文章中,會把自己實現的過程和思路循序漸進的記錄一下,相信大家看完之后,也能夠徹底理解Promise對象運行的原理,并在以后的開發中,能更熟練的使用它。

1. 回到過去: resolve, reject和then

首先來看一個Promise的使用實例:

var fn=function(resolve, reject){

console.log('begin to execute!');

var number=Math.random();

if(number<=0.5){

resolve('less than 0.5');

}else{

reject('greater than 0.5');

}

}

var p=new Promise(fn);

p.then(function(data){

console.log('resolve: ', data);

}, function(data){

console.log('reject: ', data);

})

這個例子當中,在fn當中產生一個0~1的隨機數,如果小于等于0.5, 則調用resolve函數,大于0.5,則調用reject函數。函數定義好之后,用Promise包裹這個函數,返回一個Promise對象,然后調用對象的then方法,分別定義resolve和reject函數。這里resolve和reject比較簡單,就是把傳來的參數加一個前綴然后打印輸出。

這里我們需要注意,當運行 p=new Promise(fn)這條語句的時候,fn函數就已經在執行了,然而,p.then這個方法是在后面才定義了resolve和reject,那么為何fn函數能夠知道resolve和reject函數是什么呢?

換句話說,resolve和reject函數是如何回到過去,出現在先執行的fn函數當中的呢?這是Promise當中最重要的一個概念之一。

其實想要實現這個“黑科技”,方法也非常簡單,主要運用的就是setTimeout這個方法,來延遲fn當中resolve和reject的執行。利用這個思路,我們可以初步寫出一個自己的初級版Promise,這里我們命名為MyPromise:

function MyPromise(fn) {

this.value;

this.resolveFunc = function() {};

this.rejectFunc = function() {};

fn(this.resolve.bind(this), this.reject.bind(this));

}

MyPromise.prototype.resolve = function(val) {

var self = this;

self.value=val;

setTimeout(function() {

self.resolveFunc(self.value);

}, 0);

}

MyPromise.prototype.reject = function(val) {

var self=this;

self.value=val;

setTimeout(function() {

self.rejectFunc(self.value);

}, 0);

}

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {

this.resolveFunc = resolveFunc;

this.rejectFunc = rejectFunc;

}

var fn=function(resolve, reject){

console.log('begin to execute!');

var number=Math.random();

if(number<=0.5){

resolve('less than 0.5');

}else{

reject('greater than 0.5');

}

}

var p = new MyPromise(fn);

p.then(function(data) {

console.log('resolve: ', data);

}, function(data) {

console.log('reject: ', data);

});

可以看出, MyPromise接收fn函數,并將自己的this.resolve和this.reject方法作為fn的resolve和reject參數傳給fn并執行。而我們觀察MyPromise的resolve方法,便可以發現,其主要操作,就是使用setTimeout,延遲0秒執行resolveFunc。

而再來觀察then方法,可以看到,這里比較簡單,就是接受兩個函數,并分別賦給自身的this.resolveFunc和this.rejectFunc。

這里邏輯就很清楚了,雖然fn函數首先執行,但是由于在調用resolve和reject的時候,使用了setTimeout。雖然是延遲0秒執行,但是我們知道js是單線程+消息隊列,必須等主線程代碼執行完畢才能開始執行消息隊列當中的代碼。因此,會首先執行then這個方法,給resolveFunc和rejectFunc賦值。then執行完畢后,再執行setTimeout里面的方法,這個時候,resolveFunc和rejectFunc已經被賦值了,所以就可以順利執行。這就是“回到過去”的奧秘所在。

2. 加入狀態: pending, resolved, rejected

上一節,初步實現了看起來似乎能夠運行的MyPromise,但是問題很多。我們看一下下面代碼:

var fn=function(resolve, reject){

resolve('hello');

reject('hello again');

}

var p1=new Promise(fn);

p1.then(function(data){

console.log('resolve: ',data)

}, function(data){

console.log('reject: ',data)

});

//'resolve: hello'

var p2=new MyPromise(fn);

p2.then(function(data){

console.log('resolve: ',data)

}, function(data){

console.log('reject: ',data)

});

//'resolve: hello '

//'reject: hello again'

p1是原生Promise,p2是我們自己寫的,可以看出,當調用resolve之后再調用reject,p1只會執行resolve,我們的則是兩個都執行。事實上在Promise規范當中,規定Promise只能從初始pending狀態變到resolved或者rejected狀態,是單向變化的,也就是說執行了resolve就不會再執行reject,反之亦然。

為此,我們需要在MyPromise中加入狀態,并在必要的地方進行判斷,防止重復執行:

function MyPromise(fn) {

this.value;

this.status = 'pending';

this.resolveFunc = function() {};

this.rejectFunc = function() {};

fn(this.resolve.bind(this), this.reject.bind(this));

}

MyPromise.prototype.resolve = function(val) {

var self = this;

if (this.status == 'pending') {

this.status = 'resolved';

this.value=val;

setTimeout(function() {

self.resolveFunc(self.value);

}, 0);

}

}

MyPromise.prototype.reject = function(val) {

var self = this;

if (this.status == 'pending') {

this.status = 'rejected';

this.value=val;

setTimeout(function() {

self.rejectFunc(self.value);

}, 0);

}

}

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {

this.resolveFunc = resolveFunc;

this.rejectFunc = rejectFunc;

}

這樣,再次運行上面的實例,就不會出現resolve和reject都執行的情況了。

3. 鏈式調用

在Promise的使用中,我們一定注意到,是可以鏈式調用的:

var fn=function(resolve, reject){

resolve('hello');

}

var p1=new Promise(fn);

p1.then(function(data){

console.log(data);

return 'hello again';

}).then(function(data){

console.log(data);

});

//'hello'

//'hello again'

很顯然,要實現鏈式調用,then方法的返回值也必須是一個Promise對象,這樣才能再次在后面調用then。因此我們修改MyPromise的then方法:

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {

var self = this;

return new MyPromise(function(resolve_next, reject_next) {

function resolveFuncWrap() {

var result = resolveFunc(self.value);

resolve_next(result);

}

function rejectFuncWrap() {

var result = rejectFunc(self.value);

resolve_next(result);

}

self.resolveFunc = resolveFuncWrap;

self.rejectFunc = rejectFuncWrap;

})

}

這里可以看出,then返回了一個MyPromise對象。在這個MyPromise當中,包裹了一個函數,這個函數會立即執行,主要做的事情,就是對resolveFunc和rejectFunc進行封裝,然后再賦值給前一個MyPromise的resolveFunc和rejectFunc。這里難點是看懂封裝的目的。

這里以上面一個例子來說明。在上面的鏈式調用例子中,出現了兩個Promise,第一個是我們通過new Promise顯式定義的,我們叫它Promise 1,而第二個Promise,是Promise 1的then方法返回的一個新的,我們叫它Promise 2 。在Promise 1的resolve方法執行之后,resolve的返回值,會傳遞給Promise 2的resolve作為參數,這也是為什么上面第二個then中打印出了第一個then返回的字符串。

而我們封裝的目的,就是為了讓Promise 1的resolve或者reject在執行后,將其返回值傳遞給Promise 2的resolve。在我們自己的實現中,Promise 2的resolve我們命名為resolve_next,在Promise 1的resolveFunc執行之后,我們拿到返回值result,然后調用resolve_next(result),傳遞參數給Promise 2的resolve。這里值得注意的是,無論Promise 1執行的是resolveFunc還是rejectFunc,其之后調用的,都是Promise 2的resolve,至于Promise 2的reject用來干嘛,在下面的章節里面我們會詳細描述。

至此,我們的MyPromise看起來就可以使用鏈式調用了。

然而我們再回去觀察Promise規范,會發現鏈式調用的情況也分兩種。一種情況下,前一個Promise的resolve或者reject的返回值是普通的對象,這種情況下我們目前的MyPromise可以正確處理。但還有一種情況,就是前一個Promise的resolve或者reject執行后,返回的值本身又是一個Promise對象,舉個例子:

var fn=function(resolve, reject){

resolve('hello');

}

var p1=new Promise(fn);

p1.then(function(data){

console.log(data);

return 'hello again';

}).then(function(data){

console.log(data);

return new Promise(function(resolve){

var innerData='hello third time!';

resolve(innerData);

})

}).then(function(data){

console.log(data);

});

//'hello'

//'hello again'

//'hello third time!'

在這個例子當中出現了兩次鏈式調用,第一個then返回的是一個'hello again'字符串,在第二個then的resolve中會打印處理。然后我們注意第二個then當中,返回的是一個Promise對象,調用了resolve。那么問題來了,這個resolve哪里來呢?答案就是在第三個then當中定義!這個例子中第三個then定義的resolve也比較簡單,就是直接打印傳給resolve的參數。

因此,這里我們的MyPromise也需要修改,針對前一個resolve或者reject的返回值做判斷,看是不是Promise對象,如果是,就做不同的處理,修改的代碼如下:

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {

var self = this;

return new MyPromise(function(resolve_next, reject_next) {

function resolveFuncWrap() {

var result = resolveFunc(self.value);

if (result && typeof result.then === 'function') {

//如果result是MyPromise對象,則通過then將resolve_next和reject_next傳給它

result.then(resolve_next, reject_next);

} else {

//如果result是其他對象,則作為參數傳給resolve_next

resolve_next(result);

}

}

function rejectFuncWrap() {

var result = rejectFunc(self.value);

if (result && typeof result.then === 'function') {

//如果result是MyPromise對象,則通過then將resolve_next和reject_next傳給它

result.then(resolve_next, reject_next);

} else {

//如果result是其他對象,則作為參數傳給resolve_next

resolve_next(result);

}

}

self.resolveFunc = resolveFuncWrap;

self.rejectFunc = rejectFuncWrap;

})

}

可以看到在代碼中,對于resolveFunc或者rejectFunc的返回值,我們會判斷是否含有.then方法,如果含有,就認為是一個MyPromise對象,從而調用該MyPromise的then方法,將resolve_next和reject_next傳給它。否則,正常對象,result就作為參數傳給resolve_next。

這樣修改之后,我們的MyPromise就可以在鏈式調用中正確的處理普通對象和MyPromise對象了。

如此,在這篇文章中,我們就首先實現了Promise的常用基本功能,主要是then的調用,狀態的控制,以及鏈式調用。而在后面的文章中,還會進一步講解如何實現Promise的錯誤捕獲處理等等(比如Promise當中的.catch方法原理),從而讓我們的MyPromise真正健壯和可用!

總結

以上是生活随笔為你收集整理的es5如何实现promise_彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。