溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

javascript中Promise原理是什么

發(fā)布時(shí)間:2021-11-15 15:26:00 來源:億速云 閱讀:208 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“javascript中Promise原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“javascript中Promise原理是什么”吧!

引子

為了讓大家更容易理解,我們從一個(gè)場(chǎng)景開始講解,讓大家一步一步跟著思路思考,相信你一定會(huì)更容易看懂。

考慮下面一種獲取用戶id的請(qǐng)求處理

//例1  function getUserId() {      return new Promise(function(resolve) {          //異步請(qǐng)求          http.get(url, function(results) {              resolve(results.id)          })      })  }     getUserId().then(function(id) {      //一些處理  })

getUserId方法返回一個(gè)promise,可以通過它的then方法注冊(cè)(注意注冊(cè)這個(gè)詞)在promise異步操作成功時(shí)執(zhí)行的回調(diào)。這種執(zhí)行方式,使得異步調(diào)用變得十分順手。

原理剖析

那么類似這種功能的Promise怎么實(shí)現(xiàn)呢?其實(shí)按照上面一句話,實(shí)現(xiàn)一個(gè)最基礎(chǔ)的雛形還是很easy的。

極簡(jiǎn)promise雛形

function Promise(fn) {      var value = null,          callbacks = [];  //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)         this.then = function (onFulfilled) {          callbacks.push(onFulfilled);      };         function resolve(value) {          callbacks.forEach(function (callback) {              callback(value);          });      }         fn(resolve);  }

上述代碼很簡(jiǎn)單,大致的邏輯是這樣的:

  1. 調(diào)用then方法,將想要在Promise異步操作成功時(shí)執(zhí)行的回調(diào)放入callbacks隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù),可以向觀察者模式方向思考;

  2. 創(chuàng)建Promise實(shí)例時(shí)傳入的函數(shù)會(huì)被賦予一個(gè)函數(shù)類型的參數(shù),即resolve,它接收一個(gè)參數(shù)value,代表異步操作返回的結(jié)果,當(dāng)一步操作執(zhí)行成功后,用戶會(huì)調(diào)用resolve方法,這時(shí)候其實(shí)真正執(zhí)行的操作是將callbacks隊(duì)列中的回調(diào)一一執(zhí)行;

可以結(jié)合例1中的代碼來看,首先new  Promise時(shí),傳給promise的函數(shù)發(fā)送異步請(qǐng)求,接著調(diào)用promise對(duì)象的then屬性,注冊(cè)請(qǐng)求成功的回調(diào)函數(shù),然后當(dāng)異步請(qǐng)求發(fā)送成功時(shí),調(diào)用resolve(results.id)方法,  該方法執(zhí)行then方法注冊(cè)的回調(diào)數(shù)組。

相信仔細(xì)的人應(yīng)該可以看出來,then方法應(yīng)該能夠鏈?zhǔn)秸{(diào)用,但是上面的最基礎(chǔ)簡(jiǎn)單的版本顯然無法支持鏈?zhǔn)秸{(diào)用。想讓then方法支持鏈?zhǔn)秸{(diào)用,其實(shí)也是很簡(jiǎn)單的:

this.then = function (onFulfilled) {      callbacks.push(onFulfilled);      return this;  };

see?只要簡(jiǎn)單一句話就可以實(shí)現(xiàn)類似下面的鏈?zhǔn)秸{(diào)用:

// 例2  getUserId().then(function (id) {      // 一些處理  }).then(function (id) {      // 一些處理  });

加入延時(shí)機(jī)制

細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn),上述代碼可能還存在一個(gè)問題:如果在then方法注冊(cè)回調(diào)之前,resolve函數(shù)就執(zhí)行了,怎么辦?比如promise內(nèi)部的函數(shù)是同步函數(shù):

// 例3  function getUserId() {      return new Promise(function (resolve) {          resolve(9876);      });  }  getUserId().then(function (id) {      // 一些處理  });

這顯然是不允許的,Promises/A+規(guī)范明確要求回調(diào)需要通過異步方式執(zhí)行,用以保證一致可靠的執(zhí)行順序。因此我們要加入一些處理,保證在resolve執(zhí)行之前,then方法已經(jīng)注冊(cè)完所有的回調(diào)。我們可以這樣改造下resolve函數(shù):

function resolve(value) {      setTimeout(function() {          callbacks.forEach(function (callback) {              callback(value);          });      }, 0)  }

上述代碼的思路也很簡(jiǎn)單,就是通過setTimeout機(jī)制,將resolve中執(zhí)行回調(diào)的邏輯放置到JS任務(wù)隊(duì)列末尾,以保證在resolve執(zhí)行時(shí),then方法的回調(diào)函數(shù)已經(jīng)注冊(cè)完成.

但是,這樣好像還存在一個(gè)問題,可以細(xì)想一下:如果Promise異步操作已經(jīng)成功,這時(shí),在異步操作成功之前注冊(cè)的回調(diào)都會(huì)執(zhí)行,但是在Promise異步操作成功這之后調(diào)用的then注冊(cè)的回調(diào)就再也不會(huì)執(zhí)行了,這顯然不是我們想要的。

加入狀態(tài)

恩,為了解決上一節(jié)拋出的問題,我們必須加入狀態(tài)機(jī)制,也就是大家熟知的pending、fulfilled、rejected。

Promises/A+規(guī)范中的2.1Promise  States中明確規(guī)定了,pending可以轉(zhuǎn)化為fulfilled或rejected并且只能轉(zhuǎn)化一次,也就是說如果pending轉(zhuǎn)化到fulfilled狀態(tài),那么就不能再轉(zhuǎn)化到rejected。并且fulfilled和rejected狀態(tài)只能由pending轉(zhuǎn)化而來,兩者之間不能互相轉(zhuǎn)換。一圖勝千言:

javascript中Promise原理是什么

改進(jìn)后的代碼是這樣的:

function Promise(fn) {      var state = 'pending',          value = null,          callbacks = [];         this.then = function (onFulfilled) {          if (state === 'pending') {              callbacks.push(onFulfilled);              return this;          }          onFulfilled(value);          return this;      };         function resolve(newValue) {          value = newValue;          state = 'fulfilled';          setTimeout(function () {              callbacks.forEach(function (callback) {                  callback(value);              });          }, 0);      }         fn(resolve);  }

上述代碼的思路是這樣的:resolve執(zhí)行時(shí),會(huì)將狀態(tài)設(shè)置為fulfilled,在此之后調(diào)用then添加的新回調(diào),都會(huì)立即執(zhí)行。

這里沒有任何地方將state設(shè)為rejected,為了讓大家聚焦在核心代碼上,這個(gè)問題后面會(huì)有一小節(jié)專門加入。

鏈?zhǔn)絇romise

那么這里問題又來了,如果用戶再then函數(shù)里面注冊(cè)的仍然是一個(gè)Promise,該如何解決?比如下面的例4:

// 例4  getUserId()      .then(getUserJobById)      .then(function (job) {          // 對(duì)job的處理      });     function getUserJobById(id) {      return new Promise(function (resolve) {          http.get(baseUrl + id, function(job) {              resolve(job);          });      });  }

這種場(chǎng)景相信用過promise的人都知道會(huì)有很多,那么類似這種就是所謂的鏈?zhǔn)絇romise。

鏈?zhǔn)絇romise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個(gè)promise(后鄰promise)。那么我們?nèi)绾毋暯赢?dāng)前promise和后鄰promise呢?(這是這里的難點(diǎn))。

其實(shí)也不是辣么難,只要在then方法里面return一個(gè)promise就好啦。Promises/A+規(guī)范中的2.2.7就是這么說噠(微笑臉)~

下面來看看這段暗藏玄機(jī)的then方法和resolve方法改造代碼:

function Promise(fn) {      var state = 'pending',          value = null,          callbacks = [];         this.then = function (onFulfilled) {          return new Promise(function (resolve) {              handle({                  onFulfilled: onFulfilled || null,                  resolve: resolve              });          });      };         function handle(callback) {          if (state === 'pending') {              callbacks.push(callback);              return;          }          //如果then中沒有傳遞任何東西          if(!callback.onResolved) {              callback.resolve(value);              return;          }             var ret = callback.onFulfilled(value);          callback.resolve(ret);      }               function resolve(newValue) {          if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {              var then = newValue.then;              if (typeof then === 'function') {                  then.call(newValue, resolve);                  return;              }          }          state = 'fulfilled';          value = newValue;          setTimeout(function () {              callbacks.forEach(function (callback) {                  handle(callback);              });          }, 0);      }         fn(resolve);  }

我們結(jié)合例4的代碼,分析下上面的代碼邏輯,為了方便閱讀,我把例4的代碼貼在這里:

// 例4  getUserId()      .then(getUserJobById)      .then(function (job) {          // 對(duì)job的處理      });     function getUserJobById(id) {      return new Promise(function (resolve) {          http.get(baseUrl + id, function(job) {              resolve(job);          });      });  }
  1. then方法中,創(chuàng)建并返回了新的Promise實(shí)例,這是串行Promise的基礎(chǔ),并且支持鏈?zhǔn)秸{(diào)用。

  2. handle方法是promise內(nèi)部的方法。then方法傳入的形參onFulfilled以及創(chuàng)建新Promise實(shí)例時(shí)傳入的resolve均被push到當(dāng)前promise的callbacks隊(duì)列中,這是銜接當(dāng)前promise和后鄰promise的關(guān)鍵所在(這里一定要好好的分析下handle的作用)。

  3. getUserId生成的promise(簡(jiǎn)稱getUserId  promise)異步操作成功,執(zhí)行其內(nèi)部方法resolve,傳入的參數(shù)正是異步操作的結(jié)果id

  4. 調(diào)用handle方法處理callbacks隊(duì)列中的回調(diào):getUserJobById方法,生成新的promise(getUserJobById  promise)

  5. 執(zhí)行之前由getUserId promise的then方法生成的新promise(稱為bridge  promise)的resolve方法,傳入?yún)?shù)為getUserJobById  promise。這種情況下,會(huì)將該resolve方法傳入getUserJobById promise的then方法中,并直接返回。

  6. 在getUserJobById promise異步操作成功時(shí),執(zhí)行其callbacks中的回調(diào):getUserId bridge  promise中的resolve方法

  7. ***執(zhí)行g(shù)etUserId bridge promise的后鄰promise的callbacks中的回調(diào)。

更直白的可以看下面的圖,一圖勝千言(都是根據(jù)自己的理解畫出來的,如有不對(duì)歡迎指正):

javascript中Promise原理是什么

失敗處理

在異步操作失敗時(shí),標(biāo)記其狀態(tài)為rejected,并執(zhí)行注冊(cè)的失敗回調(diào):

//例5  function getUserId() {      return new Promise(function(resolve) {          //異步請(qǐng)求          http.get(url, function(error, results) {              if (error) {                  reject(error);              }              resolve(results.id)          })      })  }     getUserId().then(function(id) {      //一些處理  }, function(error) {      console.log(error)  })

有了之前處理fulfilled狀態(tài)的經(jīng)驗(yàn),支持錯(cuò)誤處理變得很容易,只需要在注冊(cè)回調(diào)、處理狀態(tài)變更上都要加入新的邏輯:

function Promise(fn) {      var state = 'pending',          value = null,          callbacks = [];         this.then = function (onFulfilled, onRejected) {          return new Promise(function (resolve, reject) {              handle({                  onFulfilled: onFulfilled || null,                  onRejected: onRejected || null,                  resolve: resolve,                  reject: reject              });          });      };         function handle(callback) {          if (state === 'pending') {              callbacks.push(callback);              return;          }             var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,              ret;          if (cb === null) {              cb = state === 'fulfilled' ? callback.resolve : callback.reject;              cb(value);              return;          }          ret = cb(value);          callback.resolve(ret);      }         function resolve(newValue) {          if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {              var then = newValue.then;              if (typeof then === 'function') {                  then.call(newValue, resolve, reject);                  return;              }          }          state = 'fulfilled';          value = newValue;          execute();      }         function reject(reason) {          state = 'rejected';          value = reason;          execute();      }         function execute() {          setTimeout(function () {              callbacks.forEach(function (callback) {                  handle(callback);              });          }, 0);      }         fn(resolve, reject);  }

上述代碼增加了新的reject方法,供異步操作失敗時(shí)調(diào)用,同時(shí)抽出了resolve和reject共用的部分,形成execute方法。

錯(cuò)誤冒泡是上述代碼已經(jīng)支持,且非常實(shí)用的一個(gè)特性。在handle中發(fā)現(xiàn)沒有指定異步操作失敗的回調(diào)時(shí),會(huì)直接將bridge  promise(then函數(shù)返回的promise,后同)設(shè)為rejected狀態(tài),如此達(dá)成執(zhí)行后續(xù)失敗回調(diào)的效果。這有利于簡(jiǎn)化串行Promise的失敗處理成本,因?yàn)橐唤M異步操作往往會(huì)對(duì)應(yīng)一個(gè)實(shí)際功能,失敗處理方法通常是一致的:

//例6  getUserId()      .then(getUserJobById)      .then(function (job) {          // 處理job      }, function (error) {          // getUserId或者getUerJobById時(shí)出現(xiàn)的錯(cuò)誤          console.log(error);      });

異常處理

細(xì)心的同學(xué)會(huì)想到:如果在執(zhí)行成功回調(diào)、失敗回調(diào)時(shí)代碼出錯(cuò)怎么辦?對(duì)于這類異常,可以使用try-catch捕獲錯(cuò)誤,并將bridge  promise設(shè)為rejected狀態(tài)。handle方法改造如下:

function handle(callback) {      if (state === 'pending') {          callbacks.push(callback);          return;      }         var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,          ret;      if (cb === null) {          cb = state === 'fulfilled' ? callback.resolve : callback.reject;          cb(value);          return;      }      try {          ret = cb(value);          callback.resolve(ret);      } catch (e) {          callback.reject(e);      }  }

如果在異步操作中,多次執(zhí)行resolve或者reject會(huì)重復(fù)處理后續(xù)回調(diào),可以通過內(nèi)置一個(gè)標(biāo)志位解決。

到此,相信大家對(duì)“javascript中Promise原理是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI