成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

徹底理解Promise對象——用es5語法實現(xiàn)一個自己的Promise(上篇)

YJNldm / 3131人閱讀

摘要:鏈?zhǔn)秸{(diào)用在的使用中,我們一定注意到,是可以鏈?zhǔn)秸{(diào)用的很顯然,要實現(xiàn)鏈?zhǔn)秸{(diào)用,方法的返回值也必須是一個對象,這樣才能再次在后面調(diào)用。一種情況下,前一個的或者的返回值是普通的對象,這種情況下我們目前的可以正確處理。

本文同步自我的個人博客: http://mly-zju.github.io/

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

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

github源碼地址: https://github.com/mly-zju/Js-practice

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);
})

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

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

換句話說,resolve和reject函數(shù)是如何回到過去,出現(xiàn)在先執(zhí)行的fn函數(shù)當(dāng)中的呢?這是Promise當(dāng)中最重要的一個概念之一。

其實想要實現(xiàn)這個“黑科技”,方法也非常簡單,主要運用的就是setTimeout這個方法,來延遲fn當(dāng)中resolve和reject的執(zhí)行。利用這個思路,我們可以初步寫出一個自己的初級版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函數(shù),并將自己的this.resolve和this.reject方法作為fn的resolve和reject參數(shù)傳給fn并執(zhí)行。而我們觀察MyPromise的resolve方法,便可以發(fā)現(xiàn),其主要操作,就是使用setTimeout,延遲0秒執(zhí)行resolveFunc。

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

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

2. 加入狀態(tài): pending, resolved, rejected

上一節(jié),初步實現(xiàn)了看起來似乎能夠運行的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是我們自己寫的,可以看出,當(dāng)調(diào)用resolve之后再調(diào)用reject,p1只會執(zhí)行resolve,我們的則是兩個都執(zhí)行。事實上在Promise規(guī)范當(dāng)中,規(guī)定Promise只能從初始pending狀態(tài)變到resolved或者rejected狀態(tài),是單向變化的,也就是說執(zhí)行了resolve就不會再執(zhí)行reject,反之亦然。

為此,我們需要在MyPromise中加入狀態(tài),并在必要的地方進行判斷,防止重復(fù)執(zhí)行:

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;
}

這樣,再次運行上面的實例,就不會出現(xiàn)resolve和reject都執(zhí)行的情況了。

3. 鏈?zhǔn)秸{(diào)用

在Promise的使用中,我們一定注意到,是可以鏈?zhǔn)秸{(diào)用的:

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"

很顯然,要實現(xiàn)鏈?zhǔn)秸{(diào)用,then方法的返回值也必須是一個Promise對象,這樣才能再次在后面調(diào)用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當(dāng)中,包裹了一個函數(shù),這個函數(shù)會立即執(zhí)行,主要做的事情,就是對resolveFunc和rejectFunc進行封裝,然后再賦值給前一個MyPromise的resolveFunc和rejectFunc。這里難點是看懂封裝的目的。

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

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

至此,我們的MyPromise看起來就可以使用鏈?zhǔn)秸{(diào)用了。

然而我們再回去觀察Promise規(guī)范,會發(fā)現(xiàn)鏈?zhǔn)秸{(diào)用的情況也分兩種。一種情況下,前一個Promise的resolve或者reject的返回值是普通的對象,這種情況下我們目前的MyPromise可以正確處理。但還有一種情況,就是前一個Promise的resolve或者reject執(zhí)行后,返回的值本身又是一個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!"

在這個例子當(dāng)中出現(xiàn)了兩次鏈?zhǔn)秸{(diào)用,第一個then返回的是一個"hello again"字符串,在第二個then的resolve中會打印處理。然后我們注意第二個then當(dāng)中,返回的是一個Promise對象,調(diào)用了resolve。那么問題來了,這個resolve哪里來呢?答案就是在第三個then當(dāng)中定義!這個例子中第三個then定義的resolve也比較簡單,就是直接打印傳給resolve的參數(shù)。

因此,這里我們的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是其他對象,則作為參數(shù)傳給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是其他對象,則作為參數(shù)傳給resolve_next
        resolve_next(result);
      }
    }
    self.resolveFunc = resolveFuncWrap;
    self.rejectFunc = rejectFuncWrap;
  })
}

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

這樣修改之后,我們的MyPromise就可以在鏈?zhǔn)秸{(diào)用中正確的處理普通對象和MyPromise對象了。

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/86917.html

相關(guān)文章

  • 前端異步解決方案-3(Promise

    摘要:又有好些天沒有動筆了,這幾天一直在斷斷續(xù)續(xù)的學(xué)習(xí)和,今天終于能夠把著兩個玩意結(jié)合起來了解決異步問題了。今天我先把相關(guān)的用法和對異步的處理分享給大家。老樣子,還是先模擬一個。 又有好些天沒有動筆了,這幾天一直在斷斷續(xù)續(xù)的學(xué)習(xí)Promise和generator,今天終于能夠把著兩個玩意結(jié)合起來了解決異步問題了。今天我先把promise相關(guān)的用法和對異步的處理分享給大家。老樣子,還是先模擬一...

    genedna 評論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評論0 收藏0
  • JavasScript重難點知識

    摘要:忍者級別的函數(shù)操作對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機制,如果...

    forsigner 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • es6 promise面試

    摘要:執(zhí)行函數(shù)會返回一個遍歷器對象,每一次函數(shù)里面的都相當(dāng)一次遍歷器對象的方法,并且可以通過方法傳入自定義的來改變函數(shù)的行為。函數(shù)可以通過配合函數(shù)更輕松更優(yōu)雅的實現(xiàn)異步編程和控制流管理。它和構(gòu)造函數(shù)的不同點類的內(nèi)部定義的所有方法,都是不可枚舉的。 let const的命令 在ES6之前,聲明變量只能用var,var方式聲明變量其實是很不合理的,準(zhǔn)確的說,是因為ES5里面沒有塊級作用域是很不合...

    timger 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<