是一個保存了異步事件將來執行結果的對象。它不是一個新技術,而是一種能夠優化異步編程編碼風格的規範。最先誕生於社區,用於解決JavaScript代碼中回調嵌套層級過多的問題和錯誤處理邏輯堆砌的問題。使用Promise對象封裝異步操做,可讓代碼變得直觀、線性、優雅。javascript
用Promise封裝Ajax請求java
//先寫一個原始的Ajax請求 let xhr = new XMLHttpRequest() function resolve(v){console.log(v);} function reject(e){console.log(e);} xhr.onerror = function(e){ reject(e) } xhr.ontimeout = function(e){ reject(e) } xhr.onreadystatechange = function(){ if(xhr.readyState===4){ if(xhr.status===200){ resolve(xhr.response) } } } xhr.open('Get','https://wwww.google.com',true) xhr.send() // 初版:利用Promise封裝ajax let p = new Promise((resolve,reject)=>{ let xhr = new XMLHttpRequest() xhr.onerror = function(e){ reject(e) } xhr.ontimeout = function(e){ reject(e) } xhr.onreadystatechange = function(){ if(xhr.readyState===4){ if(xhr.status===200){ resolve(xhr.response) } } } xhr.open('Get','https://wwww.google.com',true) xhr.send() }); p.then(v=>{ console.log("我是成功時註冊的回調"); },e=>{ console.log("我是失敗時註冊的回調"); }) // 第二版 支持傳參、封裝了Promise建立的細節 function Xpromise(request){ function executor(request,resolve,reject){ let xhr = new XMLHttpRequest() xhr.onerror = function(e){ reject(e) } xhr.ontimeout = function(e){ reject(e) } xhr.onreadystatechange = function(){ if(xhr.readyState===4){ if(xhr.status===200){ resolve(xhr.response) } } } xhr.open('Get',request.url,true) xhr.send() } renturn new Promise(executor); } let x1 = Xpromise(makeRequest('https://wwww.google.com')).then(v=>{ console.log("我是成功時註冊的回調"); },e=>{ console.log("我是失敗時註冊的回調"); })
另外,Promise還提供了一系列好用的API,如靜態resolve()、all()、race()方法等。node
Promise用回調函數延遲綁定、回調函數onResolve返回值穿透機制解決回調嵌套層級過多的問題;使用錯誤冒泡機制簡化了錯誤處理邏輯堆砌的問題。ajax
// 第一點:Promise是一個類 class MyPromise { // 第二點:Promised構造函數的參數是一個函數; constructor(fn) { if (typeof fn !== "function") { throw new Error("promise的構造函數參數應該爲函數類型") } // 第三點:Promise的內部狀態有三個,Promise對象具備值 this._status = PENDING; this._value = undefined; // 第五點:new Promise(fn)時,就須要執行fn作業務邏輯,故構造函數裏就要調用fn函數。此處內部函數_resolve和_reject會被調用用於追蹤Promise的內部狀態 try { //注意用try-catch包住 fn(this._resolve.bind(this), this._reject.bind(this)); } catch (err) { this._reject(err) } } // 第四點:定義MyPromise狀態翻轉時,要執行的內部函數 _resolve(val){ if (this._status !== this.PENDING) return //這段代碼體現了Promise的狀態翻轉:只能是P->F或者是P->R this._status = FULLFILLED; this._value = val; }; _reject(err){ if (this._status !== this.PENDING) return this._status = REJECTED; this._value = err; }; }
class MyPromise { constructor(fn) { if (typeof fn !== "function") { throw new Error("myPromise的構造函數參數應該爲函數類型") } this._status = PENDING; this._value = undefined; //特性2-新增定義兩個回調函數數組 this.fulfilledQueue = []; this.rejectedQueue = []; try { fn(this._resolve.bind(this), this._reject.bind(this)); } catch (err) { this._reject(err) } } _resolve(val){ if (this._status !== this.PENDING) return // 特性4:註冊的回調函數在Promise狀態翻轉時會執行,執行的方式是循環從隊列裏面取出回調執行 // 定義run函數 run = (value)=>{ this._status = FULLFILLED; this._value = val; let ck; while(ck = this.fulfilledQueue.shift()){ ck(value); } } // 特性5:run()函數的執行,這裏很是關鍵。要把run放進setTimeOut。爲何? // 由於執行_resolve()函數時,then()可能還沒執行,因此爲了讓then()中的回調、包括鏈式調用的then()的回調添加到fulfilledQueue中, // 須要延遲執行run()。實際上這裏用setTimeout性能差,實際中採用微任務的方式實現 setTimeout(run,0) //run(); }; _reject(err){ if (this._status !== this.PENDING) return run = (error)=>{ this._status = this.REJECTED; this._value = err; let ck; while(ck = this.rejectedQueue.shift()){ ck(error) } } setTimeout(run,0); }; // 最重要的then函數: // 特性1-用於註冊回調,then()函數有兩個參數,都是可選的,若是參數不是函數將會被忽略 // 同一個Promise能夠屢次註冊then(),特性2-因此Promise內部要維護兩個數組,分別存儲then上註冊的成功回調和失敗回調); // then()支持鏈式調用,特性3-之因此支持是由於其返回值是一個新的Promise,此處要完成回調函數onFulfilled穿透機制的實現、錯誤冒泡機制的實現 //特性1-回調函數的註冊:故爲它傳遞兩個回調函數佔位 then(onFulfilled,onRejected){ const {_status, _value} = this; //特性3-返回一個新的promise return new MyPromise((onFulfilledNext, onRejectedNext)=>{ let fulfilled = value => { try{ if(typeof onFulfilled != "function"){ //若是 onFulfilled 或 onRejected 不是函數,onFulfilled 或 onRejected 被忽略 onFulfilledNext(value) }else{ //若是 onFulfilled 或者 onRejected 返回一個值 res let res = onFulfilled(value) if(res instanceof MyPromise){ //若 res 爲 Promise ,這時後一個回調函數,就會等待該 Promise 對象(即res )的狀態發生變化,纔會被調用,而且新的 Promise 狀態和 res 的狀態相同 res.then(onFulfilledNext, onRejectedNext) }else{ //若 res 不爲 Promise ,則使res 直接做爲新返回的 Promise 對象的值 onFulfilledNext(res) } } }catch(err){ onRejectedNext(err) } } let rejected = error => { try{ if(typeof onRejected != "function"){ onRejectedNext(error) }else{ let res = onRejectedNext(error) if(res instanceof MyPromise){ res.then(onFulfilledNext, onRejectedNext) }else{ onFulfilledNext(res); } } }catch(err){ onRejectedNext(err) } } switch(_status){ case PENDING: //在 promise 狀態改變前, onFulfilled或者onRejected不可被調用 this._fulfilledQueue.push(onFulfilled) this._rejectedQueue.push(onRejected) break case FULFILLED: //onFulfilled調用次數只有一次,fulfilled對onFulFilled進行包裝。why need 包裝?由於onFulFilled多是函數、可能不是,若是是函數,返回值多是Promise可能不是 fulfilled(_value) break case REJECTED: //onRejected調用次數只有一次 rejected(_value) break } }) } }
class MyPromise { constructor(handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } this._status = PENDING this._value = undefined this._fulfilledQueues = [] this._rejectedQueues = [] try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 添加resovle時執行的函數 _resolve(val) { const run = () => { if (this._status !== PENDING) return const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 若是resolve的參數爲Promise對象,則必須等待該Promise對象狀態改變後, 當前Promsie的狀態纔會改變,且狀態取決於參數Promsie對象的狀態, 所以這裏進行邏輯區分 */ if (val instanceof MyPromise) { val.then(value => { this._value = value this._status = FULFILLED runFulfilled(value) }, err => { this._value = err this._status = REJECTED runRejected(err) }) } else { this._value = val this._status = FULFILLED runFulfilled(val) } } setTimeout(run, 0) } _reject(err) { if (this._status !== PENDING) return const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } setTimeout(run, 0) } then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext) => { let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } switch (_status) { case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } }
class MyPromise { constructor(handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } this._status = PENDING this._value = undefined this._fulfilledQueues = [] this._rejectedQueues = [] try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } _resolve(val) { const run = () => { if (this._status !== PENDING) return const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } if (val instanceof MyPromise) { val.then(value => { this._value = value this._status = FULFILLED runFulfilled(value) }, err => { this._value = err this._status = REJECTED runRejected(err) }) } else { this._value = val this._status = FULFILLED runFulfilled(val) } } setTimeout(run, 0) } _reject(err) { if (this._status !== PENDING) return const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } setTimeout(run, 0) } then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext) => { let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } switch (_status) { case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } // 添加catch方法 catch(onRejected) { return this.then(undefined, onRejected) } // 添加靜態resolve方法 static resolve(value) { // 若是參數是MyPromise實例,直接返回這個實例 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } // 添加靜態reject方法 static reject(value) { return new MyPromise((resolve, reject) => reject(value)) } // 添加靜態all方法 static all(list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 數組參數若是不是MyPromise實例,先調用MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態就變成rejected reject(err) }) } }) } // 添加靜態race方法 static race(list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變 this.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) } finally(cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); } }
Promise是微任務的一種實現,給出以下的代碼,分析其輸出順序編程
//題目1 async function a1 () { console.log('a1 start') await a2() console.log('a1 end') } async function a2 () { console.log('a2') } console.log('script start') setTimeout(() => { console.log('setTimeout') }, 0) Promise.resolve().then(() => { console.log('promise1') }) a1() let promise2 = new Promise((resolve) => { resolve('promise2.then') console.log('promise2') }) promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log('promise3') }) }) console.log('script end') //題目2 async function async1() { console.log('async1 start'); await async2(); await async3() console.log('async1 end'); } async function async2() { console.log('async2'); } async function async3() { console.log('async3'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end'); //題目3 async function async1(){ console.log('async1 start') await async2() console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start') setTimeout(function(){ console.log('setTimeout0') },0) setTimeout(function(){ console.log('setTimeout3') },3) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick')); async1(); new Promise(function(resolve){ console.log('promise1') resolve(); console.log('promise2') }).then(function(){ console.log('promise3') }) console.log('script end')
答案:
題目一:script start->a1 start->a2->promise2->script end->promise1->a1 end->promise2.then->promise3->setTimeout
題目二:script start->async1 start->async2->promise1->script end->async3->promise2->async1 end->setTimeout
在瀏覽器console可實驗
題目三:script start->async1 start->async2->promise1->promise2
->script end->nextTick->async1 end->promise3->setTimeout->setImmediate->setTimeout3
在node環境中可實驗api
all()的實現:函數中維護了一個數組和計數器,數組的大小爲初始時all函數中傳遞的Promise對象數量,數組存儲各個Promise執行成功後resolve獲得的結果,每成功一個計數器+1,直到計數器累加到數組大小時即調用resolve(value),只要有一個Promise執行到失敗的回調,即所有失敗。
鏈式調用的實現:then函數返回的依然是一個Promise,見第二版的Promise實現。數組
方法1:使用allSettled替代;方法2:改寫Promise,將reject操做換成是resolve(new Error("自定義的錯誤"));方法3:引入第三方庫promise-transactionpromise
問題1:沒有提供中途取消的機制;問題2:必需要設置回調,不然內部錯誤沒法在外部反映出來;問題3:使用時依然存在大量Promise的api,邏輯不清晰。瀏覽器