先來看一下promise使用的一個小例子:前端
let p = new Promise(function (resolve, reject) {
console.log('start')
resolve('data1')
})
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
console.log('end')
複製代碼
運行結果以下:node
針對這個例子作如下幾點說明,也是須要直接記住的,由於這就比如是解答數學題的公式同樣,開始必定要記牢。jquery
那麼就目前的這些功能,或者說是規則,來着手寫一下MyPromise構造函數吧。promise
1 構造函數的參數,在new 的過程當中會當即執行框架
// 由於會當即執行這個執行器函數
function MyPromise(executor){
executor(resolve, reject)
}
複製代碼
2 new出來的實例具備then方法異步
MyPromise.prototype.then = function(onFulfilled, onRejected){
}
複製代碼
3 new出來的實例具備默認狀態,執行器執行resolve或者reject,修改狀態函數
function MyPromise(executor){
let self = this
self.status = 'pending' // 默認promise狀態是pending
function resolve(value){
self.status = 'resolved' // 成功狀態
}
function reject(reason){
self.status = 'rejected' //失敗狀態
}
executor(resolve, reject)
}
複製代碼
4 當執行器調用resolve後,then中第一個參數函數(成功回調)會執行,當執行器調用reject後,then中第二個參數函數(失敗回調)會執行測試
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled()
}
if(self.status === 'rejected'){
onRejected()
}
}
複製代碼
5 保證promise實例狀態一旦變動不能再次改變,只有在pending時候才能夠變狀態ui
function Promise(executor){
let self = this
self.status = 'pending' // 默認promise狀態是pending
function resolve(value){
if(self.status === 'pending'){ //保證狀態一旦變動,不能再次修改
self.value = value
self.status = 'resolved' // 成功狀態
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected' //失敗狀態
}
}
executor(resolve, reject)
}
複製代碼
6 執行器執行resolve方法傳的值,傳遞給then中第一個參數函數中this
function MyPromise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending' // 默認promise狀態是pending
function resolve(value){
if(self.status === 'pending'){ //保證狀態一旦變動,不能再次修改
self.value = value
self.status = 'resolved' // 成功狀態
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected' //失敗狀態
}
}
executor(resolve, reject) // 由於會當即執行這個執行器函數
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
}
複製代碼
嘗試使用一下這個 MyPromise :
let p = new MyPromise(function (resolve, reject) {
console.log('start')
resolve('data2')
})
p.then(
(v) => {
console.log('success ' + v)
},
(v) => {
console.log('error ' + v)
}
)
console.log('end')
複製代碼
運行結果以下:
小結:結果看似對了,不過和原生的promise仍是有不一樣的,就是success那條語句的打印順序,不要急,MyPromise 尚未寫完。
仍是看原生promise的使用小例子
let p = new Promise(function (resolve, reject) {
console.log('start')
setTimeout(function(){
resolve('data1')
},2000)
})
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
console.log('end')
複製代碼
運行結果以下
實例屢次調用then方法狀況(注意不是鏈式調用)
let p = new Promise(function (resolve, reject) {
console.log('start')
setTimeout(function(){
resolve('data1')
},2000)
})
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
console.log('end')
複製代碼
運行結果以下
那麼針對這種異步的狀況和實例p屢次調用then方法,咱們上述MyPromise該如何修改呢?
對於異步狀況,咱們先來看上面的例子,當代碼執行到了p.then() 的時候,執行器方法中的resolve('data1')被setTimeout放到了異步任務隊列中,
換句話說,也就是,此時實例p的狀態仍是默認狀態,沒有改變,那麼咱們此時並不知道要去執行then中的第一個參數(成功回調)仍是第二個參數(失敗回調)。
在不知道哪一個回調會被執行的狀況下,就須要先把這兩個回調函數保存起來,等到時機成熟,肯定調用哪一個函數的時候,再拿出來調用。
其實就是發佈訂閱的一個變種,咱們在執行一次p.then(),就會then中的參數,也就是把成功回調和失敗回調都保存起來(訂閱),執行器執行了resolve方法或者reject方法時,咱們去執行剛保存起來的函數(發佈)。
此階段MyPromise升級代碼以下
//省略其他等待,突出增長的點,等下發完整版
function MyPromise(executor){
...
// 用來保存then 方法中,第一個參數
self.onResolvedCallbacks = []
// 用來保存then 方法中,第二個參數
self.onRejectedCallbacks = []
...
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
...
if(self.status === 'pending'){
// 訂閱
self.onResolvedCallbacks.push(function(){
onFulfilled(self.value)
})
self.onRejectedCallbacks.push(function(){
onRejected(self.reason)
})
}
...
}
複製代碼
小結 這樣修改後,咱們執行器方法中,有異步函數的狀況時,p.then執行就會把對應的兩個參數保存起來了。那麼在何時調用呢?答,確定是在執行器中的resolve執行時候或者reject執行時候。
接下來貼出這階段改動的完整代碼。
function MyPromise(executor){
let self = this
self.value = undefined
self.reason = undefined
// 默認promise狀態是pending
self.status = 'pending'
// 用來保存then 方法中,第一個參數
self.onResolvedCallbacks = []
// 用來保存then 方法中,第二個參數
self.onRejectedCallbacks = []
function resolve(value){
if(self.status === 'pending'){ //保證狀態一旦變動,不能再次修改
self.value = value
self.status = 'resolved' // 成功狀態
self.onResolvedCallbacks.forEach(fn => {
fn()
})
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected' //失敗狀態
self.onRejectedCallbacks.forEach(fn => {
fn()
})
}
}
executor(resolve, reject) // 由於會當即執行這個執行器函數
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
if(self.status === 'pending'){
// 訂閱
self.onResolvedCallbacks.push(function(){
onFulfilled(self.value)
})
self.onRejectedCallbacks.push(function(){
onRejected(self.reason)
})
}
}
複製代碼
咱們來測試一下這個升級版的MyPrimise吧
let p = new MyPromise(function (resolve, reject) {
console.log('start')
setTimeout(function(){
resolve('data1')
},2000)
})
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
p.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
console.log('end')
複製代碼
運行結果以下,顯示打印start和end,兩秒後一塊兒打印的兩個 success:data1
小結: 下面這裏,爲何能拿到self.value的值,值得好好思考一下呦
self.onResolvedCallbacks.push(function(){
onFulfilled(self.value)
})
複製代碼
舒適提示,對於鏈式調用,這是手寫promise中最爲複雜的一個階段,在理解下面的操做以前,但願能夠對上面的內容再看一下,不然頗有可能形成混亂~
有以下場景,第一次讀取的是文件名字,拿到文件名字後,再去讀這個名字文件的內容。很顯然這是兩次異步操做,而且第二次的異步操做依賴第一次的異步操做結果。
// 簡要說明 建立一個js文件 與這個文件同級的 name.txt, text.txt
// 其中name.txt內容是text.txt, 而text.txt的內容是 文本1
// node 運行這個js文件
let fs = require('fs')
fs.readFile('./name.txt', 'utf8', function (err, data) {
console.log(data)
fs.readFile(data, 'utf8', function (err, data) {
console.log(data)
})
})
複製代碼
運行結果以下
很顯然,上面的回調模式不是咱們想要的,那麼咱們如何把上面寫法給promise化呢?爲了表述的更清晰一下,我仍是分步來寫:
1 封裝一個函數,函數返回promise實例
function readFile(url){
return new Promise((resolve, reject)=>{
})
}
複製代碼
2 這個函數執行就會返回promise實例,也就是有then方法可使用
readFile('./name.txt').then(
() => {},
() => {}
)
複製代碼
3 完善執行器函數,而且記住執行器函數是同步運行的,即new時候,執行器就執行了
let fs = require('fs')
function readFile(url){
return new Promise((resolve, reject)=>{
fs.readFile(url, 'utf8', function (err, data) {
if(err) reject(err)
resolve(data)
})
})
}
readFile('./name.txt').then(
(data) => { console.log(data) },
(err) => { console.log(err) }
)
複製代碼
運行一下這一小段代碼,結果以下
4 不使用鏈式調用
readFile('./name.txt').then(
(data) => {
console.log(data)
readFile(data).then(
(data) => {console.log(data)},
(err) => {console.log(err)}
)
},
(err) => {console.log(err)}
)
複製代碼
在回調里加回調,promise說你還不如不用我。運行結果以下:
5 使用鏈式調用
readFile('./name.txt')
.then(
(data) => {
console.log(data)
return readFile(data)
},
(err) => {console.log(err)}
)
.then(
(data) => { console.log(data) },
(err) => { console.log(err) }
)
複製代碼
運行結果以下
以上就是一個簡單異步場景的promise化。
其實關於鏈式調用,咱們也有一些相似於公式規則同樣的東西須要去記住,這是個規範,來自promise A+,傳送門在此 promisesaplus.com/,
我在這裏就先不羅列promise A+ 的翻譯了,先挑出幾個乾貨來,也是咱們平時使用promise習覺得常的東西。
咱們先用原生promise來驗證一下這些狀況,而後再把這些實現添加到MyPromise中。
readFile('./name.txt')
.then(
(data) => {
console.log(data)
return {'a': 100} // 1 返回引用類型
// return 100 // 2 返回基本類型
// return undefined 3 返回undefined
// 4 不寫return
},
(err) => {console.log(err)}
)
.then(
(data) => { console.log(data) },
(err) => { console.log(err) }
)
複製代碼
上面4種狀況對應 運行結果以下:
readFile('./name.txt')
.then(
(data) => {
console.log(data)
return new Promise(function(resolve, reject){
setTimeout(function(){
// resolve('ok')
reject('error')
},1000)
})
},
(err) => {console.log(err)}
)
.then(
(data) => { console.log(data) },
(err) => { console.log(err) }
)
複製代碼
運行結果以下,分別是上面執行resolve和reject的結果
readFile('./name.txt')
.then(
(data) => {
console.log(data)
throw TypeError()
},
(err) => {console.log(err)}
)
.then(
(data) => { console.log(data) },
(err) => { console.log(err) }
)
複製代碼
執行結果以下
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
return new MyPromise(function(resolve, reject){
if(self.status === 'resolved'){...}
if(self.status === 'rejected'){...}
if(self.status === 'pending'){...}
}
}
複製代碼
小結:能夠向上翻一下,對比上一版的MyPromise.prototype.then實現,其實只是本來的邏輯,用MyPromise的執行器函數包裹了一下,而咱們又知道,執行器函數是同步執行,在new 實例的時候執行器就會運行,因此就目前來看,加上這個包裹,對原有邏輯不存在什麼影響,又實現了只要then方法執行,返回的就是promise實例,而且是全新的promise實例。
MyPromise.prototype.then = function(onFulfilled, onRejected){
...
if(self.status === 'resolved'){
try{
let x = onFulfilled(self.value)
resolve(x)
}catch(e){
reject(e)
}
}
...
}
複製代碼
小結 上面代碼我只寫了 self.status === 'resolved' 這個狀態的,其他兩個狀態也是同樣的寫法,我就先拿這一個舉例說明。onFulfilled,就是咱們的promise實例,執行then方法傳的第一個參數,他執行後返回普通值的話,會直接把這個值傳遞給鏈式調用的下一個then的成功回調函數中。(這個表述你們應該能夠看懂吧)。
好,咱們來想一下,經過第一步,已經實現了then方法返回全新的promise,那麼,這個全新的promise再去執行then的話,這個then的成功回調和失敗回調的參數,也就是這個then的第一個參數須要的value和第二個參數須要的reason,哪裏來?
確定是在這個全新的promise實例的,new 過程當中,那個處理器函數中的,resolve或者reject。這裏實際上是有些繞的。
爲了更好的理解上面說的,我再來個圖,回顧下以前的例子
輸出的是什麼呢? 你們都知道 會先輸出 success Ace 後輸出 success undefined
因此,上面圖中,第一個then返回了新的promise不假,可是沒有執行resolve和reject,這種狀況就至關於 resolve(undefined) , 因此第二個then,打印的是 success undefined
因此這一小節中的,let x = onFulfilled(self.value) 這裏的起因,我囉嗦的挺多了吧~固然,這只是處理普通值的狀況。附上這階段的完整代碼。
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
let promise2 = new MyPromise(function(resolve, reject){
// then 函數的成功回調函數的執行結果 與 promise2的關係
if(self.status === 'resolved'){
try{
let x = onFulfilled(self.value)
resolve(x) // 這是 x 是常量的時候,但x多是一個新的promise,
}catch(e){
reject(e)
}
}
if(self.status === 'rejected'){
try{
let x = onRejected(self.reason)
resolve(x)
}catch(e){
reject(e)
}
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(function(){
try{
let x = onFulfilled(self.value)
resolve(x)
}catch(e){
reject(e)
}
})
self.onRejectedCallbacks.push(function(){
try{
let x = onRejected(self.reason)
resolve(x)
}catch(e){
reject(e)
}
})
}
})
return promise2
}
複製代碼
測試上面代碼示例以下
let p = new MyPromise(function (resolve, reject) {
console.log('start')
setTimeout(function(){
resolve('data1')
},500)
})
p.then(
(v) => {
console.log('success: ' + v)
// return v // 1 返回 v
// return 100 // 2 返回常量
// return {a : 100} // 3 返回對象
// return undefined // 4 返回 undefined
// 5 不寫return
},
(v) => {
console.log('error: ' + v)
}
)
.then(
(v) => {
console.log('success: ' + v)
},
(v) => {
console.log('error: ' + v)
}
)
console.log('end')
複製代碼
對應上面1--5的結果以下
也就是說對於上面例子,出現了第六種狀況,既,then的第一個回調函數,返回了一個新的promise實例
p.then(
(v) => {
console.log('success: ' + v)
return new MyPromise(excutor)
},
(v) => {
console.log('error: ' + v)
}
)
複製代碼
then的第一個回調函數,對應MyPromise的是onFulfilled,因此咱們要對MyPromise.prototype.then 再次改造
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this
let promise2 = new MyPromise(function(resolve, reject){
// then 函數的成功回調函數的執行結果 與 promise2的關係
if(self.status === 'resolved'){
try{
let x = onFulfilled(self.value)
// x多是一個新的promise , 抽離一個函數來處理x的狀況
resolvePromise(promise2, x, resolve, reject)
}catch(e){
reject(e)
}
}
if(self.status === 'rejected'){...}
if(self.status === 'pending'){...}
})
return promise2
}複製代碼