自定義程式碼
這裡是我自定義的Promise,如果想看原版,可以跳過最後有符合PromiseA+規範的原始碼
class 承諾 {
constructor(處理器函式) { //1. 處理器函式是在_new 承諾(處理器函式)_的時候當做引數傳入到建構函式裡的,建構函式裡會執行這個處理器函式.
let self = this //2.註冊函式執行時可能沒有字首,如果註冊函式裡用this可能就是window,這裡把this賦給變數,形成閉包.
this.狀態 = '等待...' //3.這個屬性有三個值,預設是等待,只有呼叫註冊成功或註冊失敗才會改變這個屬性的值,**只能改變一次**
this.成功值 = undefined //4.這個成功值是由使用者呼叫註冊成功函式的時候傳進函式裡的,然後我們在註冊成功函式給這個屬性賦值,簡單點說就是形參賦值給屬性
this.失敗原因 = undefined //5.同上
//說明:這裡需要了解'那時()'函式,可以看完'那時()'再來看,這兩在非同步的情況才會用到,使用者在呼叫'那時()'的時候,承諾仍然是等待狀態(也就是說註冊成功和註冊失敗兩個函式沒有被呼叫)
this.成功回撥函式組 = [] //6.承諾變為成功時要呼叫的函式,多次呼叫then傳函式,我們就把'那時()'傳進來的函式push到這個陣列裡,
this.失敗回撥函式組 = [] //7.同上,承諾變為失敗時要呼叫的函式
let 註冊成功 = function (成功值) { //7.'註冊成功()'函式當實參傳進了處理器函式,這樣使用者在編寫處理器函式得時候可以根據情況呼叫.
if (self.狀態 === '等待...') { //8.這個判斷的作用:承諾的狀態只能由等待->成功或等待->失敗,而且只能改變一次.我們預設初始態是等待,如果不是等待說明之前已經呼叫過'註冊成功或註冊失敗'
self.成功值 = 成功值 //9.將使用者呼叫'註冊成功(成功值)'函式傳進來的值由物件屬性儲存,這個值在呼叫'那時()'傳進來的函式時會用到
self.狀態 = '成功' //10.呼叫註冊成功,改變承諾狀態為成功
for (let 函式 of self.成功回撥函式組) { //11.呼叫'那時()'傳進來的函式
函式(成功值)
}
// this.成功回撥函式組.forEach(函式=>函式())
}
}
let 註冊失敗 = function (失敗原因) { //12. 同上
if (self.狀態 === '等待...') {
self.失敗原因 = 失敗原因
self.狀態 = '失敗'
for (let 函式 of self.失敗回撥函式組) {
函式(失敗原因)
}
// this.成功回撥函式組.forEach(函式=>函式())
}
}
try {
處理器函式(註冊成功, 註冊失敗) //13.執行處理器函式,把我們定義好的兩個函式傳進去,這樣使用者就可以用這兩個函式來改變承諾的狀態和值
} catch (錯誤) {
註冊失敗(錯誤) //14.處理器函式是使用者編寫的,有可能報錯,出錯了我們就呼叫註冊失敗()來改變這個承諾的狀態和值
}
}
那時(成功的回撥, 失敗的回撥) { //15.物件的'那時()'方法,由使用者傳進來兩個函式引數,規定當前承諾物件為成功時呼叫第一個,失敗呼叫第二個
let self = this
成功的回撥 = typeof 成功的回撥 === 'function' ? 成功的回撥 : 成功值 => 成功值
失敗的回撥 = typeof 失敗的回撥 === 'function' ? 失敗的回撥 : 失敗原因 => {
throw 失敗原因
} //16.這兩個引數是可選引數,當他們不是函式時我們給他預設值
let 新的承諾 = new 承諾((註冊成功, 註冊失敗) => { //17.返回一個新的承諾,這樣就可以鏈式呼叫'那時()'了,這裡要注意新承諾的狀態和值由'那時()'傳進來的函式執行情況決定
//18.判斷承諾的狀態,決定立即執行回撥還是將回撥函式push到回撥函式組裡等使用者呼叫註冊成功()在註冊成功()裡執行
if (self.狀態 === '等待...') { //19.如果是等待,顯然承諾的成功值或失敗值還是undefined,所以我們把他push到回撥函式組裡,讓他在註冊成功()或註冊失敗()函式裡呼叫
self.成功回撥函式組.push(() => { //20.這個函式要非同步,因為我們會在裡面用到新承諾,然而新承諾現在還沒被賦值,要徹底理解這裡應該需要js執行機制知識,目前我還沒有....
setTimeout(() => {
try {
let 回撥的返回值 = 成功的回撥(self.成功值) //21.呼叫使用者傳進來的函式得到返回值,如果使用者沒有寫返回語句預設是返回undefined
善後處理(新的承諾, 回撥的返回值, 註冊成功, 註冊失敗) //22.根據返回值決定我們這個新承諾的值和狀態 這裡傳進去的是我們新承諾的註冊成功和註冊失敗函式,我們在裡面呼叫他們來改變我們新承諾的狀態
} catch (錯誤) {
註冊失敗(錯誤) //23.如果執行使用者的函式出錯就把我們新承諾的狀態和值改變
}
})
})
self.失敗回撥函式組.push(() => { //24.同上
setTimeout(() => {
try {
let 回撥的返回值 = 失敗的回撥(self.失敗原因)
善後處理(新的承諾, 回撥的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
})
}
if (self.狀態 === '成功') { //25.如果承諾的狀態是成功的說明註冊成功()函式已經被呼叫了,承諾的狀態和值都被使用者改變了,
//我們可以取到對應的值來傳進回撥裡,讓使用者用.
setTimeout(() => {
try { //邏輯同20-23
let 回撥的返回值 = 成功的回撥(self.成功值)
善後處理(新的承諾, 回撥的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
}
if (self.狀態 === '失敗') { //同上
setTimeout(() => {
try {
let 回撥的返回值 = 失敗的回撥(self.失敗原因)
善後處理(新的承諾, 回撥的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
}
})
return 新的承諾
}
}
工具函式,這個函式才是真核心
//26.這個是核心,涉及到遞迴,這裡我寫的和PromiseA+規範的實現不同,他判斷的是thenable,相容性好,我只是為了理解Promise原理所以就簡化了.
function 善後處理(新的承諾, 回撥的返回值, 註冊成功, 註冊失敗) {
// let p2 = p.那時((成功值)=>{
// return p2
// })
if (回撥的返回值 instanceof 承諾) { //27.判斷使用者寫的回撥函式的返回值是不是一個承諾,如果不是承諾就直接呼叫我們新承諾的註冊成功()函式改變我們新承諾的狀態和值
//如果返回值是一個承諾我們就得到這個承諾的值,把這個值給我們新承諾的註冊函式
if (新的承諾 === 回撥的返回值) {
//28.這裡是為了解決這種使用情況
// let p2 = p.那時((成功值)=>{
// return p2
// })
註冊失敗(new TypeError('迴圈引用'))
return
}
try {
回撥的返回值.那時((成功值) => {
善後處理(新的承諾, 成功值, 註冊成功, 註冊失敗) //29.如果使用者返回的承諾的值還是一個承諾,繼續'那時()'直到不是承諾
//注意:這裡傳進去的註冊成功,註冊失敗是我們新承諾的註冊函式,遞迴進去,當不是承諾時就改變我們新承諾的狀態和值了,然後遞迴一層層返回
//這裡是進遞迴,其他情況就是出遞迴
}, (失敗原因) => {
註冊失敗(失敗原因)
})
} catch (錯誤) {
註冊失敗(錯誤)
}
} else {
註冊成功(回撥的返回值)
}
}
自定義完成,我們拉出來遛一遛
//測試一
p = new 承諾((註冊成功, 註冊失敗) => {
setTimeout(()=>{
註冊失敗('abc')
},1000)
})
p.那時((成功的值) => {
console.log(成功的值)
},(失敗原因)=>{
console.log(失敗原因)
})
//輸出abc
//測試二:返回值是承諾
p = new 承諾((註冊成功, 註冊失敗) => {
setTimeout(() => {
註冊成功('我是最外面的')
}, 1000)
})
p.那時((成功值) => {
console.log(成功值)
let p11 = new 承諾((註冊成功, 註冊失敗) => {
let p22 = new 承諾((註冊成功, 註冊失敗) => {
註冊成功('最裡層')
})
註冊成功(p22)
})
return p11
}, 1)
.那時((成功值) => {
console.log(成功值)
}, (失敗原因) => {
console.log(失敗原因)
})
//輸出:
//我是最外層
//我是最裡層
//測試三:失敗穿透
p = new 承諾((註冊成功, 註冊失敗) => {
throw '(╥╯^╰╥)'
setTimeout(() => {
註冊成功('♪(´▽`)')
}, 1000)
})
p.那時((成功的值) => {
console.log(成功的值)
},(失敗原因)=>{
console.log('第一次失敗:'+失敗原因)
throw '(╥╯^╰╥))'
}).那時(
(成功的值) => {
console.log(成功的值)
}
).那時(null,(失敗原因) => {
console.log('失敗穿透'+失敗原因)
})
//輸出
//第一次失敗:(╥╯^╰╥)
//失敗穿透(╥╯^╰╥))
到這裡就結束了,下面是通過PromiseA+測試的原始碼.
function Promise(executor) {
let self = this;
self.value = undefined; // 成功的值
self.reason = undefined; // 失敗的值
self.status = 'pending'; // 目前promise的狀態pending
self.onResolvedCallbacks = []; // 可能new Promise的時候會存在非同步操作,把成功和失敗的回撥儲存起來
self.onRejectedCallbacks = [];
function resolve(value) { // 把狀態更改為成功
if (self.status === 'pending') { // 只有在pending的狀態才能轉為成功態
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時非同步操作,存在的成功回撥儲存起來
}
}
function reject(reason) { // 把狀態更改為失敗
if (self.status === 'pending') { // 只有在pending的狀態才能轉為失敗態
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時非同步操作,存在的失敗回撥儲存起來
}
}
try {
// 在new Promise的時候,立即執行的函式,稱為執行器
executor(resolve, reject);
} catch (e) { // 如果執行executor丟擲錯誤,則會走失敗reject
reject(e);
}
}
// then呼叫的時候,都是屬於非同步,是一個微任務
// 微任務會比巨集任務先執行
// onFulfilled為成功的回撥,onRejected為失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
let self = this;
let promise2;
// 上面講了,promise和jquery的區別,promise不能單純返回自身,
// 而是每次都是返回一個新的promise,才可以實現鏈式呼叫,
// 因為同一個promise的pending resolve reject只能更改一次
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
// 為什麼要加setTimeout?
// 首先是promiseA+規範要求的
// 其次是大家寫的程式碼,有的是同步,有的是非同步
// 所以為了更加統一,就使用為setTimeout變為非同步了,保持一致性
setTimeout(() => {
try { // 上面executor雖然使用try catch捕捉錯誤
// 但是在非同步中,不一定能夠捕捉,所以在這裡
// 用try catch捕捉
let x = onFulfilled(self.value);
// 在then中,返回值可能是一個promise,所以
// 需要resolvePromise對返回值進行判斷
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
}
if (self.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
}
});
return promise2
}
function resolvePromise(promise2, x, resolve, reject) {
// 3.從2中我們可以得出,自己不能等於自己
// 當promise2和x是同一個物件的時候,則走reject
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 4.因為then中的返回值可以為promise,當x為物件或者函式,才有可能返回的是promise
let called
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 8.從第7步,可以看出為什麼會存在丟擲異常的可能,所以使用try catch處理
try {
// 6.因為當x為promise的話,是存在then方法的
// 但是我們取一個物件上的屬性,也有可能出現異常,我們可以看一下第7步
let then = x.then
// 9.我們為什麼在這裡用call呢?解決了什麼問題呢?可以看上面的第10步
// x可能還是個promise,那麼就讓這個promise執行
// 但是還是存在一個惡作劇的情況,就是{then:{}}
// 此時需要新增一個判斷then是否函式
if (typeof then === 'function') {
then.call(x, (y) => { // y是返回promise後的成功結果
// 一開始我們在這裡寫的是resolve(y),但是考慮到一點
// 這個y,有可能還是一個promise,
// 也就是說resolve(new Promise(...))
// 所以涉及到遞迴,我們把resolve(y)改成以下
// 12.限制既調resolve,也調reject
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
// 這樣的話,程式碼會一直遞迴,取到最後一層promise
// 11.這裡有一種情況,就是不能既調成功也調失敗,只能挑一次,
// 但是我們前面不是處理過這個情況了嗎?
// 理論上是這樣的,但是我們前面也說了,resolvePromise這個函式
// 是所有promise通用的,也可以是別人寫的promise,如果別人
// 的promise可能既會調resolve也會調reject,那麼就會出問題了,所以我們接下來要
// 做一下限制,這個我們寫在第12步
}, (err) => { // err是返回promise後的失敗結果
if (called) return
called = true
reject(err)
})
} else {
if (called) return;
called = true;
resolve(x) // 如果then不是函式的話,那麼則是普通物件,直接走resolve成功
}
} catch (e) { // 當出現異常則直接走reject失敗
if (called) return
called = true
reject(e)
}
} else { // 5.x為一個常量,則是走resolve成功
resolve(x)
}
}
module.exports = Promise;
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
//執行命令promises-aplus-tests promise.js檢測是否符合promiseA+規範,先安裝包