Promise介紹--規範篇

本篇文章是Promise系列文章的第二篇,主要是講解基於Promise/A+規範,在傳入不一樣類型的參數時,promise內部分別會如何處理。本章的主要目的是讓你們對promise有一個更加深刻的理解,也爲下一篇講如何實現一個promise庫作準備。(寫完以後以爲好水。。。)git

英文版本的規範見這裏,segmentfault上也有人把規範翻譯爲中文,見這裏github

在此,我主要是經過使用例子,講解一下規範中then方法和Promise Resolution Procedure的每一種狀況。web

constructor

規範中對於構造函數沒有明確說明,因此在此處拿出來說解一下。segmentfault

和普通JavaScript對象同樣,咱們一樣是經過new關鍵詞來建立一個Promise對象實例。構造函數只接收一個參數,且該參數必須是一個函數,任何其餘的值好比undefinednull5true等都會報一個TypeError的錯誤。例:promise

new Promise(true)
// Uncaught TypeError: Promise resolver true is not a function(…)

一樣,若是你沒有經過new關鍵詞建立,而是直接執行Promise(),一樣也會報一個TypeError的錯誤。session

Promise()
// Uncaught TypeError: undefined is not a promise(…)

因此,咱們必須經過new Promise(function()=>{})的方式來建立一個Promise實例。一般咱們見到的建立一個Promise實例的代碼以下:異步

var promise = new Promise(function(resolve, reject) {
    // 進行一些異步操做
    // 而後調用resolve或reject方法
});

這纔是正確的姿式~ 從該例子中,咱們能夠看到建立Promise實例時傳入的函數,同時還接受兩個參數,它們分別對應Promise內部實現的兩個方法。上一篇文章中,我提到過Promise有三種狀態,pendingfulfilledrejected,實例剛建立時處於pending狀態,當執行reject方法時,變爲rejected狀態,以下所示:函數

new Promise(function(resolve, reject){
    reject(Promise.resolve(5))
}).then(function(value){
    console.log('fulfill', value)
}, function(reason){
    console.log('reject', reason)
})
// reject Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 5}

而當執行resolve方法時,它可能變爲fulfilled,也有可能變爲rejected狀態。也就是說resolve != fulfill。以下:oop

new Promise(function(resolve, reject){
    resolve(Promise.reject(5))
}).then(function(value){
    console.log('fulfill', value)
}, function(reason){
    console.log('reject', reason)
})
// reject 5

那麼resolve是個什麼東西呢?它是根據什麼變爲fulfilledrejected的呢?這就是咱們接下來要講解的Promise Resolution Procedure,我把它稱做「Promise處理程序」。測試

Promise Resolution Procedure

講以前,咱們先說幾個promise規範中的幾個術語。

promise 它是一個擁有then方法的對象或函數,且符合該規範
thenable 擁有then方法的對象或函數
value 是指一個合法的 Javascript
exception throw語句拋出的異常
reason 描述promise爲何失敗的值

Promise Resolution Procedure是對傳入的promise和value進行抽象操做。咱們可一個把它理解成resolve(promise, value),對參數promise和value進行一系列處理操做。下面咱們按照規範中的順序,依次介紹每種狀況。

2.3.1 若是promisevalue指向同一個對象,則rejectpromise並以一個TypeError做爲reason

var defer = {}
var promise = new Promise(function(resolve){ 
    defer.resolve = resolve
})
promise.catch(function(reason){
    console.log(reason)
})
defer.resolve(promise)
// TypeError: Chaining cycle detected for promise #<Promise>(…)

咱們把resolve函數保存在defer中,這樣就能夠在外部對promise進行狀態改變,defer.resolve(promise)中的promise正是咱們建立的對象,根據規範拋出了TypeError

2.3.2 若是value是一個promise對象,且是基於當前實現建立的。

2.3.2.1 若是value處於pending狀態,則promise一樣pending並直到value狀態改變。
2.3.2.2 若是value處於fulfilled狀態,則使用相同的value值fulfill promise
2.3.2.3 若是value處於rejected狀態,則使用相同的reason值reject promise

var promise1 = new Promise((resolve) => {
    setTimeout(() => {
        resolve(5)
    },3000)
});
console.time('fulfill')
var promise = new Promise((resolve) => {
    resolve(promise1)
})
promise.then((value) => {
    console.timeEnd('fulfill')
    console.log('fulfill', value)
})
setTimeout(()=>{
    console.log('setTimeout', promise)
}, 1000)

// setTimeout Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// fulfill: 3.01e+03ms
// fulfill 5

經過該例子能夠看出,最後setTimeout延遲1秒查看promise狀態時,它依然處於pending狀態,當3秒後promise1變爲fulfilled後,promise隨即變爲fulfilled並以5做爲value傳給then添加的成功回調函數中。

var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('error'))
    }, 3000)
});
console.time('reject')
var promise = new Promise((resolve) => {
    resolve(promise1)
})
promise.catch((reason) => {
    console.timeEnd('reject')
    console.log('reject', reason)
})
setTimeout(()=>{
    console.log('setTimeout', promise)
}, 1000)

// setTimeout Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// reject: 3e+03ms
// reject Error: error(…)

失敗時例子與成功時相似。

2.3.3 若是value是一個對象或函數
2.3.3.1 使then等於value.then
2.3.3.2 若是獲取value.then的值時拋出異常,這經過該異常reject promise,例:

new Promise((resolve)=>{
    resolve({then:(()=>{
        throw new Error('error')
        })()
    })
}).catch((reason)=>{
    console.log(reason)
})
// Error: error(…)

上例中獲取value.then時,會拋出異常

2.3.3.3 若是then是一個函數,則把value做爲函數中this指向來調用它,第一個參數是resolvePromise,第二個參數是rejectPromise

其實這裏主要是爲了兼容兩種狀況,第一種是傳入的value是個Deferred對象,則狀態和Deferred對象一致;另外一種狀況是否是使用當前構造函數建立的Promise對象,經過這種方式能夠兼容,達到一致的效果。

2.3.3.3.1 若是resolvePromise經過傳入y來調用,則執行resolve(promise, y),例:

new Promise((resolve)=>{
    resolve({then:(resolvePromise, rejectPromise)=>{
        resolvePromise(5)
        }
    })
}).then((value)=>{
    console.log(value)
})
// 5

2.3.3.3.2 若是rejectPromise 經過傳入緣由r來調用,則傳入rreject promise,例:

new Promise((resolve)=>{
    resolve({then:(resolvePromise, rejectPromise)=>{
        rejectPromise(new Error('error'))
        }
    })
}).catch((reason)=>{
    console.log(reason)
})
// Error: error(…)

2.3.3.3.3 若是resolvePromiserejectPromise都被調用,或其中一個被調用了屢次,則以第一次調用的爲準,並忽略以後的調用。例:

new Promise((resolve)=>{
    resolve({then:(resolvePromise, rejectPromise)=>{
        resolvePromise(5)
        rejectPromise(new Error('error'))
        }
    })
}).then((value)=>{
    console.log(value)
}, (reason)=>{
    console.log(reason)
})
// 5

2.3.3.3.4 若是調用then拋出異常e:

2.3.3.3.4.1 若是resolvePromiserejectPromise已經調用,則忽略它,例:

new Promise((resolve)=>{
    resolve({then:(resolvePromise, rejectPromise)=>{
        resolvePromise(5)
        throw new Error('error')
        }
    })
}).then((value)=>{
    console.log(value)
}, (reason)=>{
    console.log(reason)
})
// 5

2.3.3.3.4.2 不然,則傳入ereject promise,例:

new Promise((resolve)=>{
    resolve({then:(resolvePromise, rejectPromise)=>{
        throw new Error('error')
        }
    })
}).then((value)=>{
    console.log(value)
}, (reason)=>{
    console.log(reason)
})
// Error: error(…)

2.3.3.4 若是then不是一個函數,則傳入valuefulfill promise,例:

new Promise((resolve)=>{
    resolve({then:5})
}).then((value)=>{
    console.log(value)
}, (reason)=>{
    console.log(reason)
})
// Object {then: 5}

then 方法

一個promise必須提供一個then方法來處理成功或失敗。

then方法接收兩個參數:

promise.then(onFulfilled, onRejected)

2.2.1 onFulfilledonRejected都是可選的
2.2.1.1 若是onFulfilled不是一個函數,則忽略。例:

Promise.resolve(5)
    .then(true,function(reason){
        console.log(reason)
    })
    .then(function(value){
        console.log(value)
    })
// 5

2.2.1.2 若是onRejected不是一個函數,則忽略。例:

Promise.reject(new Error('error'))
    .then(true,null)
    .then(undefined,function(reason){
        console.log(reason)
    })

// Error: error(…)

2.2.2 若是onFulfilled是一個函數
2.2.2.1 它必須在promise變爲fulfilled以後調用,且把promisevalue做爲它的第一個參數

這個從咱們全部的例子中均可以看出

2.2.2.2 它不能夠在promise變爲fulfilled以前調用

var defer = {}
console.time('fulfill')
var promise = new Promise((resolve)=>{
    defer.resolve = resolve
});
promise.then((value)=>{
    console.timeEnd('fulfill')
})
setTimeout(()=>{
    defer.resolve(5)
},1000);
// fulfill: 1e+03ms

onFulfilled執行的時間能夠看出promise直到變爲fulfilled後才調用

2.2.2.3 它只能夠被調用一次

var defer = {}
var promise = new Promise((resolve)=>{
    defer.resolve = resolve
});
promise.then((value)=>{
    console.log(value++)
})
defer.resolve(5)
// 5
defer.resolve(6)
// 後面再也不次執行

2.2.3 若是onRejected是一個函數
2.2.3.1 它必須在promise變爲rejected以後調用,且把promisereason做爲它的第一個參數
2.2.3.2 它不能夠在promise變爲rejected以前調用
2.2.3.3 它只能夠被調用一次

onRejectedonFulfilled基本相似,這裏再也不次贅述

2.2.4 onFulfilledonRejected是在執行環境中僅包含平臺代碼時調用

這裏有一個備註,平臺代碼是指引擎、執行環境、以及promise的實現代碼。實際過程當中,要確保onFulfilledonRejected是異步執行的,它是在event loop過程當中then方法被調用以後的新調用棧中執行。咱們可使用setTimeoutsetImmediatemacro-task機制來實現,也可使用MutationObserverprocess.nextTickmicro-task機制來實現。promise的實現自己就被看做是平臺代碼,它自己就包含一個處理器能夠調用的任務調度隊列。

才疏學淺,沒理解它這一條到底要表達一個什麼意思。。。應該指的就是異步執行,由於異步執行的時候,頁面中同步的邏輯都已經執行完畢,因此只剩下平臺代碼。

注:原生的Promise實現屬於micro-task機制。macro-taskmicro-task分別是兩種異步任務,它們的不一樣後面會單獨講一下。下面列出了常見的異步方法都屬於那種異步機制:

macro-task: script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, 原生Promise, Object.observe, MutationObserver

2.2.5 onFulfilledonRejected必須做爲函數來調用,沒有this

Promise.resolve(5).then(function(){
    console.log(this)
})
// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

2.2.6 同一個promise上的then方法可能會調用屢次
2.2.6.1 若是promise fulfilled,則全部的onFulfilled回調函數按照它們添加的順序依次調用。

var defer = {}
var promise = new Promise((resolve)=>{
    defer.resolve = resolve
});
promise.then((value)=>{
    console.log(1,value++)
})
promise.then((value)=>{
    console.log(2,value++)
})
promise.then((value)=>{
    console.log(3,value++)
})
defer.resolve(5)

// 1 5
// 2 5
// 3 5

2.2.6.2 若是promise rejected,則全部的onRejected回調函數按照它們添加的順序依次調用。

例子與上例相似

2.2.7 then方法會返回一個全新的promise

promise2 = promise1.then(onFulfilled, onRejected);

2.2.7.1 若是onFulfilledonRejected返回了一個值x,則執行resolve(promise2, x)

Promise.resolve(5).then(function(value){
    return ++value
}).then(function(value){
    console.log(value)
})
// 6

2.2.7.2 若是onFulfilledonRejected拋出了異常e,則reject promise2並傳入緣由e

Promise.resolve(5).then(function(value){
    throw new Error('error')
}).catch(function(reason){
    console.log(reason)
})
// Error: error(…)

2.2.7.3 若是onFulfilled不是一個函數且promise1 fulfilled,則promise2以一樣的value fulfill

Promise.resolve(5).then("tiaoguo").then(function(value){
    console.log(value)
})
// 5

2.2.7.4 若是onRejected不是一個函數且promise1 rejected,則promise2以一樣的reason reject

Promise.reject(new Error('error')).catch('tiaoguo').catch(function(reason){
    console.log(reason)
})
// Error: error(…)

更多的測試代碼,你們能夠去promises-tests中查看,這是一個基於規範的promise測試庫。

以上基本是整個Promise/A+規範的全部內容,若有錯誤,歡迎批評指正。下一篇我會根據規範一步一步實現一個Promise polyfill庫。

相關文章
相關標籤/搜索