Promise原理講解 && 實(shí)現(xiàn)一個Promise對象 (遵循Promise/A+規(guī)范)

1.什么是Promise?

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

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

回調(diào)函數(shù)

事件監(jiān)聽

發(fā)布/訂閱(深入了解發(fā)布/訂閱

Promise對象

這里就拿回調(diào)函數(shù)說說

(1) 對于回調(diào)函數(shù) 我們用Jquery的ajax獲取數(shù)據(jù)時 都是以回調(diào)函數(shù)方式獲取的數(shù)據(jù)

$.get(url, (data) => {

console.log(data)

)

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

$.get(url, data1 => {

console.log(data1)

$.get(data1.url, data2 => {

console.log(data1)

})

})

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

代碼邏輯書寫順序與執(zhí)行順序不一致于樟,不利于閱讀與維護(hù)艘希。

異步操作的順序變更時硼身,需要大規(guī)模的代碼重構(gòu)。

回調(diào)函數(shù)基本都是匿名函數(shù)覆享,bug 追蹤困難佳遂。

回調(diào)函數(shù)是被第三方庫代碼(如上例中的 ajax )而非自己的業(yè)務(wù)代碼所調(diào)用的,造成了 IoC 控制反轉(zhuǎn)撒顿。

Promise 處理多個相互關(guān)聯(lián)的異步請求

(1) 而我們Promise 可以更直觀的方式 來解決 "回調(diào)地獄"

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 請求數(shù)據(jù) 也都支持 Promise API

import axios from 'axios';

axios.get(url).then(data => {

console.log(data)

})

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

3.Promise使用

Promise 是一個構(gòu)造函數(shù), new Promise 返回一個 promise對象 接收一個excutor執(zhí)行函數(shù)作為參數(shù), excutor有兩個函數(shù)類型形參resolve reject

const promise = new Promise((resolve, reject) => {

// 異步處理

// 處理結(jié)束后吩屹、調(diào)用resolve 或 reject

});

復(fù)制代碼

promise相當(dāng)于一個狀態(tài)機(jī)

promise的三種狀態(tài)

pending

fulfilled

rejected

(1) promise 對象初始化狀態(tài)為 pending

(2) 當(dāng)調(diào)用resolve(成功)跪另,會由pending => fulfilled

(3) 當(dāng)調(diào)用reject(失敗),會由pending => rejected

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

promise對象方法

(1) then方法注冊 當(dāng)resolve(成功)/reject(失敗)的回調(diào)函數(shù)

// onFulfilled 是用來接收promise成功的值

// onRejected 是用來接收promise失敗的原因

promise.then(onFulfilled, onRejected);

復(fù)制代碼

注意:then方法是異步執(zhí)行的

(2) resolve(成功) onFulfilled會被調(diào)用

const promise = new Promise((resolve, reject) => {

resolve('fulfilled'); // 狀態(tài)由 pending => fulfilled

});

promise.then(result => { // onFulfilled

console.log(result); // 'fulfilled'

}, reason => { // onRejected 不會被調(diào)用

})

(3) reject(失敗) onRejected會被調(diào)用

const promise = new Promise((resolve, reject) => {

reject('rejected'); // 狀態(tài)由 pending => rejected

});

promise.then(result => { // onFulfilled 不會被調(diào)用

}, reason => { // onRejected

console.log(reason); // 'rejected'

})

(4) promise.catch

在鏈?zhǔn)綄懛ㄖ锌梢圆东@前面then中發(fā)送的異常,

promise.catch(onRejected)

相當(dāng)于

promise.then(null, onRrejected);

// 注意

// onRejected 不能捕獲當(dāng)前onFulfilled中的異常

promise.then(onFulfilled, onRrejected);

// 可以寫成:

promise.then(onFulfilled)

.catch(onRrejected);

promise chain

promise.then方法每次調(diào)用 都返回一個新的promise對象 所以可以鏈?zhǔn)綄懛?/p>

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方法中的異常

Promise的靜態(tài)方法

(1) Promise.resolve 返回一個fulfilled狀態(tài)的promise對象

Promise.resolve('hello').then(function(value){

console.log(value);

});

Promise.resolve('hello');

// 相當(dāng)于

const promise = new Promise(resolve => {

resolve('hello');

});

(2) Promise.reject 返回一個rejected狀態(tài)的promise對象

Promise.reject(24);

new Promise((resolve, reject) => {

reject(24);

});

(3) Promise.all 接收一個promise對象數(shù)組為參數(shù)

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

const p1 = new Promise((resolve, reject) => {

resolve(1);

});

const p2 = new Promise((resolve, reject) => {

resolve(2);

});

const p3 = new Promise((resolve, reject) => {

resolve(3);

});

Promise.all([p1, p2, p3]).then(data => {

console.log(data); // [1, 2, 3] 結(jié)果順序和promise實(shí)例數(shù)組順序是一致的

}, err => {

console.log(err);

});

(4) Promise.race 接收一個promise對象數(shù)組為參數(shù)

Promise.race 只要有一個promise對象進(jìn)入 FulFilled 或者 Rejected 狀態(tài)的話煤搜,就會繼續(xù)進(jìn)行后面的處理免绿。

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

});

4.Promise 代碼實(shí)現(xiàn)

/**

* Promise 實(shí)現(xiàn) 遵循promise/A+規(guī)范

* Promise/A+規(guī)范譯文:

* https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4

*/

// promise 三個狀態(tài)

const PENDING = "pending";

const FULFILLED = "fulfilled";

const REJECTED = "rejected";

function Promise(excutor) {

let that = this; // 緩存當(dāng)前promise實(shí)例對象

that.status = PENDING; // 初始狀態(tài)

that.value = undefined; // fulfilled狀態(tài)時 返回的信息

that.reason = undefined; // rejected狀態(tài)時 拒絕的原因

that.onFulfilledCallbacks = []; // 存儲fulfilled狀態(tài)對應(yīng)的onFulfilled函數(shù)

that.onRejectedCallbacks = []; // 存儲rejected狀態(tài)對應(yīng)的onRejected函數(shù)

function resolve(value) { // value成功態(tài)時接收的終值

if(value instanceof Promise) {

return value.then(resolve, reject);

}

// 為什么resolve 加setTimeout?

// 2.2.4規(guī)范 onFulfilled 和 onRejected 只允許在 execution context 棧僅包含平臺代碼時運(yùn)行.

// 注1 這里的平臺代碼指的是引擎、環(huán)境以及 promise 的實(shí)施代碼擦盾。實(shí)踐中要確保 onFulfilled 和 onRejected 方法異步執(zhí)行嘲驾,且應(yīng)該在 then 方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行。

setTimeout(() => {

// 調(diào)用resolve 回調(diào)對應(yīng)onFulfilled函數(shù)

if (that.status === PENDING) {

// 只能由pending狀態(tài) => fulfilled狀態(tài) (避免調(diào)用多次resolve reject)

that.status = FULFILLED;

that.value = value;

that.onFulfilledCallbacks.forEach(cb => cb(that.value));

}

});

}

function reject(reason) { // reason失敗態(tài)時接收的拒因

setTimeout(() => {

// 調(diào)用reject 回調(diào)對應(yīng)onRejected函數(shù)

if (that.status === PENDING) {

// 只能由pending狀態(tài) => rejected狀態(tài) (避免調(diào)用多次resolve reject)

that.status = REJECTED;

that.reason = reason;

that.onRejectedCallbacks.forEach(cb => cb(that.reason));

}

});

}

// 捕獲在excutor執(zhí)行器中拋出的異常

// new Promise((resolve, reject) => {

// throw new Error('error in excutor')

// })

try {

excutor(resolve, reject);

} catch (e) {

reject(e);

}

}

/**

* resolve中的值幾種情況:

* 1.普通值

* 2.promise對象

* 3.thenable對象/函數(shù)

*/

/**

* 對resolve 進(jìn)行改造增強(qiáng) 針對resolve中不同值情況 進(jìn)行處理

* @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 就會導(dǎo)致循環(huán)引用報錯

return reject(new TypeError('循環(huán)引用'));

}

let called = false; // 避免多次調(diào)用

// 如果x是一個promise對象 (該判斷和下面 判斷是不是thenable對象重復(fù) 所以可有可無)

if (x instanceof Promise) { // 獲得它的終值 繼續(xù)resolve

if (x.status === PENDING) { // 如果為等待態(tài)需等待直至 x 被執(zhí)行或拒絕 并解析y值

x.then(y => {

resolvePromise(promise2, y, resolve, reject);

}, reason => {

reject(reason);

});

} else { // 如果 x 已經(jīng)處于執(zhí)行態(tài)/拒絕態(tài)(值已經(jīng)被解析為普通值)迹卢,用相同的值執(zhí)行傳遞下去 promise

x.then(resolve, reject);

}

// 如果 x 為對象或者函數(shù)

} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {

try { // 是否是thenable對象(具有then方法的對象/函數(shù))

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 { // 說明是一個普通對象/函數(shù)

resolve(x);

}

} catch(e) {

if(called) return;

called = true;

reject(e);

}

} else {

resolve(x);

}

}

/**

* [注冊fulfilled狀態(tài)/rejected狀態(tài)對應(yīng)的回調(diào)函數(shù)]

* @param {function} onFulfilled fulfilled狀態(tài)時 執(zhí)行的函數(shù)

* @param {function} onRejected rejected狀態(tài)時 執(zhí)行的函數(shù)

* @return {function} newPromsie 返回一個新的promise對象

*/

Promise.prototype.then = function(onFulfilled, onRejected) {

const that = this;

let newPromise;

// 處理參數(shù)默認(rèn)值 保證參數(shù)后續(xù)能夠繼續(xù)執(zhí)行

onFulfilled =

typeof onFulfilled === "function" ? onFulfilled : value => value;

onRejected =

typeof onRejected === "function" ? onRejected : reason => {

throw reason;

};

// then里面的FULFILLED/REJECTED狀態(tài)時 為什么要加setTimeout ?

// 原因:

// 其一 2.2.4規(guī)范 要確保 onFulfilled 和 onRejected 方法異步執(zhí)行(且應(yīng)該在 then 方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行) 所以要在resolve里加上setTimeout

// 其二 2.2.6規(guī)范 對于一個promise辽故,它的then方法可以調(diào)用多次.(當(dāng)在其他程序中多次調(diào)用同一個promise的then時 由于之前狀態(tài)已經(jīng)為FULFILLED/REJECTED狀態(tài),則會走的下面邏輯),所以要確保為FULFILLED/REJECTED狀態(tài)后 也要異步執(zhí)行onFulfilled/onRejected

// 其二 2.2.6規(guī)范 也是resolve函數(shù)里加setTimeout的原因

// 總之都是 讓then方法異步執(zhí)行 也就是確保onFulfilled/onRejected異步執(zhí)行

// 如下面這種情景 多次調(diào)用p1.then

// p1.then((value) => { // 此時p1.status 由pending狀態(tài) => fulfilled狀態(tài)

// console.log(value); // resolve

// // console.log(p1.status); // fulfilled

// p1.then(value => { // 再次p1.then 這時已經(jīng)為fulfilled狀態(tài) 走的是fulfilled狀態(tài)判斷里的邏輯 所以我們也要確保判斷里面onFuilled異步執(zhí)行

// console.log(value); // 'resolve'

// });

// console.log('當(dāng)前執(zhí)行棧中同步代碼');

// })

// console.log('全局執(zhí)行棧中同步代碼');

//

if (that.status === FULFILLED) { // 成功態(tài)

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) { // 失敗態(tài)

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) { // 等待態(tài)

// 當(dāng)異步調(diào)用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進(jìn)行并行處理

* 參數(shù): promise對象組成的數(shù)組作為參數(shù)

* 返回值: 返回一個Promise實(shí)例

* 當(dāng)這個數(shù)組里的所有promise對象全部變?yōu)閞esolve狀態(tài)的時候腐碱,才會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

* 參數(shù): 接收 promise對象組成的數(shù)組作為參數(shù)

* 返回值: 返回一個Promise實(shí)例

* 只要有一個promise對象進(jìn)入 FulFilled 或者 Rejected 狀態(tài)的話,就會繼續(xù)進(jìn)行后面的處理(取決于哪一個更快)

*/

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實(shí)現(xiàn)Deferred的

* Deferred和Promise的關(guān)系

* - Deferred 擁有 Promise

* - Deferred 具備對 Promise的狀態(tài)進(jìn)行操作的特權(quán)方法(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+規(guī)范測試

* npm i -g promises-aplus-tests

* promises-aplus-tests Promise.js

*/

try {

module.exports = Promise

} catch (e) {

}

5.Promise測試

npm i -g promises-aplus-tests

promises-aplus-tests Promise.js

需要源碼可私我

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喻杈,一起剝皮案震驚了整個濱河市彤枢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筒饰,老刑警劉巖缴啡,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓷们,居然都是意外死亡业栅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門谬晕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碘裕,“玉大人,你說我怎么就攤上這事攒钳“锟祝” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵不撑,是天一觀的道長文兢。 經(jīng)常有香客問我,道長焕檬,這世上最難降的妖魔是什么姆坚? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮实愚,結(jié)果婚禮上兼呵,老公的妹妹穿的比我還像新娘兔辅。我一直安慰自己,他們只是感情好击喂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布维苔。 她就那樣靜靜地躺著,像睡著了一般茫负。 火紅的嫁衣襯著肌膚如雪蕉鸳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天忍法,我揣著相機(jī)與錄音,去河邊找鬼榕吼。 笑死饿序,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羹蚣。 我是一名探鬼主播原探,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼顽素!你這毒婦竟也來了咽弦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胁出,失蹤者是張志新(化名)和其女友劉穎型型,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體全蝶,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡闹蒜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抑淫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绷落。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖始苇,靈堂內(nèi)的尸體忽然破棺而出砌烁,到底是詐尸還是另有隱情,我是刑警寧澤催式,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布函喉,位于F島的核電站,受9級特大地震影響蓄氧,放射性物質(zhì)發(fā)生泄漏函似。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一喉童、第九天 我趴在偏房一處隱蔽的房頂上張望撇寞。 院中可真熱鬧顿天,春花似錦、人聲如沸蔑担。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啤握。三九已至鸟缕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間排抬,已是汗流浹背懂从。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹲蒲,地道東北人番甩。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像届搁,于是被迫代替她去往敵國和親缘薛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容