Promise 原理解析與實現(遵循Promise/A+規范)

1.什么是Promise?

Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一

2.對于幾種常見異步編程方案

  • 回調函數
  • 事件監聽
  • 發布/訂閱
  • Promise對象

這里就拿回調函數說說

1.對于回調函數 我們用Jquery的ajax獲取數據時 都是以回調函數方式獲取的數據

$.get(url, (data) => {console.log(data)
)
復制代碼

2.如果說 當我們需要發送多個異步請求 并且每個請求之間需要相互依賴 那這時 我們只能 以嵌套方式來解決 形成 "回調地獄"

$.get(url, data1 => {console.log(data1)$.get(data1.url, data2 => {console.log(data1)})
})
復制代碼

這樣一來,在處理越多的異步邏輯時,就需要越深的回調嵌套,這種編碼模式的問題主要有以下幾個:

  • 代碼邏輯書寫順序與執行順序不一致,不利于閱讀與維護。
  • 異步操作的順序變更時,需要大規模的代碼重構。
  • 回調函數基本都是匿名函數,bug 追蹤困難。
  • 回調函數是被第三方庫代碼(如上例中的 ajax )而非自己的業務代碼所調用的,造成了 IoC 控制反轉。

Promise 處理多個相互關聯的異步請求

1.而我們Promise 可以更直觀的方式 來解決 "回調地獄"

const request = url => { return new Promise((resolve, reject) => {$.get(url, data => {resolve(data)});})
};// 請求data1
request(url).then(data1 => {return request(data1.url);   
}).then(data2 => {return request(data2.url);
}).then(data3 => {console.log(data3);
}).catch(err => throw new Error(err));
復制代碼

2.相信大家在 vue/react 都是用axios fetch 請求數據 也都支持 Promise API

import axios from 'axios';
axios.get(url).then(data => {console.log(data)
})
復制代碼

Axios 是一個基于 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。

3.Promise使用

1.Promise 是一個構造函數, new Promise 返回一個 promise對象 接收一個excutor執行函數作為參數, excutor有兩個函數類型形參resolve reject

const promise = new Promise((resolve, reject) => {// 異步處理// 處理結束后、調用resolve 或 reject
});復制代碼

2.promise相當于一個狀態機

promise的三種狀態

  • pending
  • fulfilled
  • rejected

1.promise 對象初始化狀態為 pending 2.當調用resolve(成功),會由pending => fulfilled 3.當調用reject(失敗),會由pending => rejected

注意promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變

3.promise對象方法

1.then方法注冊 當resolve(成功)/reject(失敗)的回調函數

// onFulfilled 是用來接收promise成功的值
// onRejected 是用來接收promise失敗的原因
promise.then(onFulfilled, onRejected);
復制代碼

then方法是異步執行的

2.resolve(成功) onFulfilled會被調用

const promise = new Promise((resolve, reject) => {resolve('fulfilled'); // 狀態由 pending => fulfilled
});
promise.then(result => { // onFulfilledconsole.log(result); // 'fulfilled' 
}, reason => { // onRejected 不會被調用})
復制代碼

3.reject(失敗) onRejected會被調用

const promise = new Promise((resolve, reject) => {reject('rejected'); // 狀態由 pending => rejected
});
promise.then(result => { // onFulfilled 不會被調用}, reason => { // onRejected console.log(reason); // 'rejected'
})
復制代碼

4.promise.catch

在鏈式寫法中可以捕獲前面then中發送的異常,

promise.catch(onRejected)
相當于
promise.then(null, onRrejected);// 注意
// onRejected 不能捕獲當前onFulfilled中的異常
promise.then(onFulfilled, onRrejected); // 可以寫成:
promise.then(onFulfilled).catch(onRrejected);   
復制代碼

4.promise chain

promise.then方法每次調用 都返回一個新的promise對象 所以可以鏈式寫法

function taskA() {console.log("Task A");
}
function taskB() {console.log("Task B");
}
function onRejected(error) {console.log("Catch Error: A or B", error);
}var promise = Promise.resolve();
promise.then(taskA).then(taskB).catch(onRejected) // 捕獲前面then方法中的異常
復制代碼

5.Promise的靜態方法

1.Promise.resolve 返回一個fulfilled狀態的promise對象

Promise.resolve('hello').then(function(value){console.log(value);
});Promise.resolve('hello');
// 相當于
const promise = new Promise(resolve => {resolve('hello');
});
復制代碼

2.Promise.reject 返回一個rejected狀態的promise對象

Promise.reject(24);
new Promise((resolve, reject) => {reject(24);
});
復制代碼

3.Promise.all 接收一個promise對象數組為參數

只有全部為resolve才會調用 通常會用來處理 多個并行異步操作

const p1 = new Promise((resolve, reject) => {resolve(1);
});const p2 = new Promise((resolve, reject) => {resolve(2);
});const p3 = new Promise((resolve, reject) => {reject(3);
});Promise.all([p1, p2, p3]).then(data => { console.log(data); // [1, 2, 3] 結果順序和promise實例數組順序是一致的
}, err => {console.log(err);
});
復制代碼

4.Promise.race 接收一個promise對象數組為參數

Promise.race 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行后面的處理。

function timerPromisefy(delay) {return new Promise(function (resolve, reject) {setTimeout(function () {resolve(delay);}, delay);});
}
var startDate = Date.now();Promise.race([timerPromisefy(10),timerPromisefy(20),timerPromisefy(30)
]).then(function (values) {console.log(values); // 10
});
復制代碼

5.Promise的finally

Promise.prototype.finally = function (callback) {let P = this.constructor;return this.then(value  => P.resolve(callback()).then(() => value),reason => P.resolve(callback()).then(() => { throw reason }));
};
復制代碼

4. Promise 代碼實現

/*** Promise 實現 遵循promise/A+規范* Promise/A+規范譯文:* https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4*/// promise 三個狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";function Promise(excutor) {let that = this; // 緩存當前promise實例對象that.status = PENDING; // 初始狀態that.value = undefined; // fulfilled狀態時 返回的信息that.reason = undefined; // rejected狀態時 拒絕的原因that.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數that.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數function resolve(value) { // value成功態時接收的終值if(value instanceof Promise) {return value.then(resolve, reject);}// 為什么resolve 加setTimeout?// 2.2.4規范 onFulfilled 和 onRejected 只允許在 execution context 棧僅包含平臺代碼時運行.// 注1 這里的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環之后的新執行棧中執行。setTimeout(() => {// 調用resolve 回調對應onFulfilled函數if (that.status === PENDING) {// 只能由pending狀態 => fulfilled狀態 (避免調用多次resolve reject)that.status = FULFILLED;that.value = value;that.onFulfilledCallbacks.forEach(cb => cb(that.value));}});}function reject(reason) { // reason失敗態時接收的拒因setTimeout(() => {// 調用reject 回調對應onRejected函數if (that.status === PENDING) {// 只能由pending狀態 => rejected狀態 (避免調用多次resolve reject)that.status = REJECTED;that.reason = reason;that.onRejectedCallbacks.forEach(cb => cb(that.reason));}});}// 捕獲在excutor執行器中拋出的異常// new Promise((resolve, reject) => {//     throw new Error('error in excutor')// })try {excutor(resolve, reject);} catch (e) {reject(e);}
}/*** resolve中的值幾種情況:* 1.普通值* 2.promise對象* 3.thenable對象/函數*//*** 對resolve 進行改造增強 針對resolve中不同值情況 進行處理* @param  {promise} promise2 promise1.then方法返回的新的promise對象* @param  {[type]} x         promise1中onFulfilled的返回值* @param  {[type]} resolve   promise2的resolve方法* @param  {[type]} reject    promise2的reject方法*/
function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {  // 如果從onFulfilled中返回的x 就是promise2 就會導致循環引用報錯return reject(new TypeError('循環引用'));}let called = false; // 避免多次調用// 如果x是一個promise對象 (該判斷和下面 判斷是不是thenable對象重復 所以可有可無)if (x instanceof Promise) { // 獲得它的終值 繼續resolveif (x.status === PENDING) { // 如果為等待態需等待直至 x 被執行或拒絕 并解析y值x.then(y => {resolvePromise(promise2, y, resolve, reject);}, reason => {reject(reason);});} else { // 如果 x 已經處于執行態/拒絕態(值已經被解析為普通值),用相同的值執行傳遞下去 promisex.then(resolve, reject);}// 如果 x 為對象或者函數} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {try { // 是否是thenable對象(具有then方法的對象/函數)let then = x.then;if (typeof then === 'function') {then.call(x, y => {if(called) return;called = true;resolvePromise(promise2, y, resolve, reject);}, reason => {if(called) return;called = true;reject(reason);})} else { // 說明是一個普通對象/函數resolve(x);}} catch(e) {if(called) return;called = true;reject(e);}} else {resolve(x);}
}/*** [注冊fulfilled狀態/rejected狀態對應的回調函數]* @param  {function} onFulfilled fulfilled狀態時 執行的函數* @param  {function} onRejected  rejected狀態時 執行的函數* @return {function} newPromsie  返回一個新的promise對象*/
Promise.prototype.then = function(onFulfilled, onRejected) {const that = this;let newPromise;// 處理參數默認值 保證參數后續能夠繼續執行onFulfilled =typeof onFulfilled === "function" ? onFulfilled : value => value;onRejected =typeof onRejected === "function" ? onRejected : reason => {throw reason;};// then里面的FULFILLED/REJECTED狀態時 為什么要加setTimeout ?// 原因:// 其一 2.2.4規范 要確保 onFulfilled 和 onRejected 方法異步執行(且應該在 then 方法被調用的那一輪事件循環之后的新執行棧中執行) 所以要在resolve里加上setTimeout// 其二 2.2.6規范 對于一個promise,它的then方法可以調用多次.(當在其他程序中多次調用同一個promise的then時 由于之前狀態已經為FULFILLED/REJECTED狀態,則會走的下面邏輯),所以要確保為FULFILLED/REJECTED狀態后 也要異步執行onFulfilled/onRejected// 其二 2.2.6規范 也是resolve函數里加setTimeout的原因// 總之都是 讓then方法異步執行 也就是確保onFulfilled/onRejected異步執行// 如下面這種情景 多次調用p1.then// p1.then((value) => { // 此時p1.status 由pedding狀態 => fulfilled狀態//     console.log(value); // resolve//     // console.log(p1.status); // fulfilled//     p1.then(value => { // 再次p1.then 這時已經為fulfilled狀態 走的是fulfilled狀態判斷里的邏輯 所以我們也要確保判斷里面onFuilled異步執行//         console.log(value); // 'resolve'//     });//     console.log('當前執行棧中同步代碼');// })// console.log('全局執行棧中同步代碼');//if (that.status === FULFILLED) { // 成功態return newPromise = new Promise((resolve, reject) => {setTimeout(() => {try{let x = onFulfilled(that.value);resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一個onFulfilled的返回值} catch(e) {reject(e); // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected);}});})}if (that.status === REJECTED) { // 失敗態return newPromise = new Promise((resolve, reject) => {setTimeout(() => {try {let x = onRejected(that.reason);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});});}if (that.status === PENDING) { // 等待態// 當異步調用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中return newPromise = new Promise((resolve, reject) => {that.onFulfilledCallbacks.push((value) => {try {let x = onFulfilled(value);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});that.onRejectedCallbacks.push((reason) => {try {let x = onRejected(reason);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});});}
};/*** Promise.all Promise進行并行處理* 參數: promise對象組成的數組作為參數* 返回值: 返回一個Promise實例* 當這個數組里的所有promise對象全部變為resolve狀態的時候,才會resolve。*/
Promise.all = function(promises) {return new Promise((resolve, reject) => {let done = gen(promises.length, resolve);promises.forEach((promise, index) => {promise.then((value) => {done(index, value)}, reject)})})
}function gen(length, resolve) {let count = 0;let values = [];return function(i, value) {values[i] = value;if (++count === length) {console.log(values);resolve(values);}}
}/*** Promise.race* 參數: 接收 promise對象組成的數組作為參數* 返回值: 返回一個Promise實例* 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行后面的處理(取決于哪一個更快)*/
Promise.race = function(promises) {return new Promise((resolve, reject) => {promises.forEach((promise, index) => {promise.then(resolve, reject);});});
}// 用于promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
Promise.prototype.catch = function(onRejected) {return this.then(null, onRejected);
}Promise.resolve = function (value) {return new Promise(resolve => {resolve(value);});
}Promise.reject = function (reason) {return new Promise((resolve, reject) => {reject(reason);});
}/*** 基于Promise實現Deferred的* Deferred和Promise的關系* - Deferred 擁有 Promise* - Deferred 具備對 Promise的狀態進行操作的特權方法(resolve reject)**參考jQuery.Deferred*url: http://api.jquery.com/category/deferred-object/*/
Promise.deferred = function() { // 延遲對象let defer = {};defer.promise = new Promise((resolve, reject) => {defer.resolve = resolve;defer.reject = reject;});return defer;
}/*** Promise/A+規范測試* npm i -g promises-aplus-tests* promises-aplus-tests Promise.js*/try {module.exports = Promise
} catch (e) {
}復制代碼

Promise測試

npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
復制代碼

如何主動終止Promise調用鏈

const p1 = new Promise((resolve, reject) => {setTimeout(() => { // 異步操作resolve('start')}, 1000);
});p1.then((result) => {console.log('a', result); return Promise.reject('中斷后續調用'); // 此時rejected的狀態將直接跳到catch里,剩下的調用不會再繼續
}).then(result => {console.log('b', result);
}).then(result => {console.log('c', result);
}).catch(err => {console.log(err);
});// a start
// 中斷后續調用
復制代碼

相關知識參考資料

  • ES6-promise
  • Promises/A+規范-英文
  • Promises/A+規范-翻譯1
  • Promises/A+規范-翻譯-推薦
  • JS執行棧
  • Javascript異步編程的4種方法

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/391441.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/391441.shtml
英文地址,請注明出處:http://en.pswp.cn/news/391441.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

php 數據訪問練習:投票頁面

<!--投票界面--> <html> <head> <title></title> <meta charset"UTF-8"/> <link rel"stylesheet" type"text/css" href"bootstrap.min.css"/> <script src"bootstrap.min.js"…

深入理解InnoDB(3)—索引的存儲結構

1. 索引的各種存儲結構及其優缺點 1.1 二叉樹 優點&#xff1a; 二叉樹是一種比順序結構更加高效地查找目標元素的結構&#xff0c;它可以從第一個父節點開始跟目標元素值比較&#xff0c;如果相等則返回當前節點&#xff0c;如果目標元素值小于當前節點&#xff0c;則移動到左…

有抱負/初級開發人員的良好習慣-避免使用的習慣

When youre learning to code, it can be easy to pick up some nasty habits along the way. Here are some tips to avoid common bad habits, and the good habits to keep in mind.當您學習編碼時&#xff0c;很容易在此過程中養成一些討厭的習慣。 這里有一些技巧&#xf…

業精于勤荒于嬉---Go的GORM查詢

查詢 //通過主鍵查詢第一條記錄 db.First(&user)SELECT * FROM users ORDER BY id LIMIT 1;// 隨機取一條記錄 db.Take(&user)SELECT * FROM users LIMIT 1;// 通過主鍵查詢最后一條記錄 db.Last(&user)SELECT * FROM users ORDER BY id DESC LIMIT 1;// 拿到所有的…

apache 虛擬主機詳細配置:http.conf配置詳解

Apache的配置文件http.conf參數含義詳解 Apache的配置由httpd.conf文件配置&#xff0c;因此下面的配置指令都是在httpd.conf文件中修改。主站點的配置(基本配置) (1) 基本配置:ServerRoot "/mnt/software/apache2" #你的apache軟件安裝的位置。其它指定的目錄如果沒…

深入理解InnoDB(4)—索引使用

1. 索引的代價 在了解索引的代價之前&#xff0c;需要再次回顧一下索引的數據結構B樹 如上圖&#xff0c;是一顆b樹&#xff0c;關于b樹的定義可以參見B樹&#xff0c;這里只說一些重點&#xff0c;淺藍色的塊我們稱之為一個磁盤塊&#xff0c;可以看到每個磁盤塊包含幾個數據…

[BZOJ1626][Usaco2007 Dec]Building Roads 修建道路

1626: [Usaco2007 Dec]Building Roads 修建道路 Time Limit: 5 Sec Memory Limit: 64 MB Submit: 1730 Solved: 727 [Submit][Status][Discuss]Description Farmer John最近得到了一些新的農場&#xff0c;他想新修一些道路使得他的所有農場可以經過原有的或是新修的道路互達…

雙城記s001_雙城記! (使用數據講故事)

雙城記s001Keywords: Data science, Machine learning, Python, Web scraping, Foursquare關鍵字&#xff1a;數據科學&#xff0c;機器學習&#xff0c;Python&#xff0c;Web抓取&#xff0c;Foursquare https://br.pinterest.com/pin/92816442292506979/https://br.pintere…

python:linux中升級python版本

https://www.cnblogs.com/gne-hwz/p/8586430.html 轉載于:https://www.cnblogs.com/gcgc/p/11446403.html

web前端面試總結

2019獨角獸企業重金招聘Python工程師標準>>> 摘要&#xff1a;前端的東西特別多&#xff0c;面試的時候我們如何從容應對&#xff0c;作為一個老兵&#xff0c;我在這里分享幾點我的經驗。 一、javascript 基礎(es5) 1、原型&#xff1a;這里可以談很多&#xff0c;…

783. 二叉搜索樹節點最小距離(dfs)

給你一個二叉搜索樹的根節點 root &#xff0c;返回 樹中任意兩不同節點值之間的最小差值 。 注意&#xff1a;本題與 530&#xff1a;https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/ 相同 示例 1&#xff1a; 輸入&#xff1a;root [4,2,6,1,3] 輸…

linux epoll機制對TCP 客戶端和服務端的監聽C代碼通用框架實現

1 TCP簡介 tcp是一種基于流的應用層協議&#xff0c;其“可靠的數據傳輸”實現的原理就是&#xff0c;“擁塞控制”的滑動窗口機制&#xff0c;該機制包含的算法主要有“慢啟動”&#xff0c;“擁塞避免”&#xff0c;“快速重傳”。 2 TCP socket建立和epoll監聽實現 數據結構…

linux中安裝robot環境

https://www.cnblogs.com/lgqboke/p/8252488.html&#xff08;文中驗證robotframework命令應該為 robot --version&#xff09; 可能遇到的問題&#xff1a; 1、python版本太低 解決&#xff1a;升級python https://www.cnblogs.com/huaxingtianxia/p/7986734.html 2、pip安裝報…

angular 模塊構建_我如何在Angular 4和Magento上構建人力資源門戶

angular 模塊構建Sometimes trying a new technology mashup works wonders. Both Magento 2 Angular 4 are very commonly talked about, and many consider them to be the future of the development industry. 有時嘗試新技術的mashup會產生奇跡。 Magento 2 Angular 4都…

tableau破解方法_使用Tableau瀏覽Netflix內容的簡單方法

tableau破解方法Are you struggling to perform EDA with R and Python?? Here is an easy way to do exploratory data analysis using Tableau.您是否正在努力使用R和Python執行EDA&#xff1f; 這是使用Tableau進行探索性數據分析的簡單方法。 Lets Dive in to know the …

六周第三次課

2019獨角獸企業重金招聘Python工程師標準>>> 六周第三次課 9.6/9.7 awk awk也是流式編輯器&#xff0c;針對文檔中的行來操作&#xff0c;一行一行地執行。 awk比sed更強大的功能是它支持了分段。 -F選項的作用是指定分隔符&#xff0c;如果不加-F選項&#xff0c;…

面試題字符集和編碼區別_您和理想工作之間的一件事-編碼面試!

面試題字符集和編碼區別A recruiter calls you for a position with your dream company. You get extremely excited and ask about their recruiting process. He replies saying “Its nothing big, you will have 5 coding rounds with our senior tech team, just the sta…

初探Golang(1)-變量

要學習golang&#xff0c;當然要先配置好相關環境啦。 1. Go 安裝包下載 https://studygolang.com/dl 在Windows下&#xff0c;直接下載msi文件&#xff0c;在安裝界面選擇安裝路徑&#xff0c;然后一直下一步就行了。 在cmd下輸入 go version即可看到go安裝成功 2. Golan…

macaca web(4)

米西米西滴&#xff0c;吃過中午飯來一篇&#xff0c;話說&#xff0c;上回書說道macaca 測試web&#xff08;3&#xff09;&#xff0c;參數驅動來搞&#xff0c;那么有小伙本又來給雷子來需求&#xff0c; 登錄模塊能不能給我給重新封裝一下嗎&#xff0c; 我說干嘛封裝&…

linux中安裝cx_Oracle

https://blog.csdn.net/w657395940/article/details/41144225 各種嘗試都&#xff0c;最后 pip install cx-Oracle 成功導入 轉載于:https://www.cnblogs.com/gcgc/p/11447583.html