Promise
在平常使用中很是普遍,本身也是零零散散的學習了一部分,直到有一天本身查看axios
的源碼時,發現裏面大量的應用了Promise
的函數,決定系統的學習一下。本文主要是根據Promise/A+
的規範,進行Promise
的學習和實現;ios
Promise
是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。 所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise
是一個對象,從它能夠獲取異步操做的消息。Promise
提供統一的 API,各類異步操做均可以用一樣的方法進行處理。git
pengding(進行中,初始狀態),fulfilled(已成功)、reject(已失敗)
;只有異步操做結果,纔可以改變當前的狀態,其餘的手段沒法改變pending->fulfilled和pending變爲reject
,只要有這兩種狀況發生,狀態就會凝固,不會在發生改變了pending
的狀態時候,沒法獲得目前是哪個階段;想要實現某個內容,就應該明確Promise
的方法使用; 使用方法可參照:es6.ruanyifeng.com/#docs/promi…
本次手寫也是參照此用法,對輸入和輸出進行的控制
Promise
做爲一個構造函數,其在new的時候就是馬上進行執行,根據其屬性和行爲去構建基礎執行流程,new Promise()自己是一個同步的函數,而Promise.then纔是異步的函數
;es6
查看Promise A+規範對於狀態的要求
Promies/A+規範要求:github
Promise
的狀態是pending的時候,可能會轉化到 fulfilled或者rejected
狀態Promise
狀態是filfilled
的時候
Promise
的狀態是reject
的時候
const PENDING = 'pending';
const RESOLVED = 'fulfilled'; //成功
const REJECTED = 'rejected' //失敗
複製代碼
關鍵點面試
status
resolve
和reject
來接收成功和失敗時候狀態更改new Promise
時候返回成功和失敗狀態;//建立Promise的基本類
class Promise {
//看這個屬性 可以在原型上使用 看屬性是否公用
constructor(executor) {
this.status = PENDING;
//成功的值
this.value = undefined;
//失敗的緣由
this.reason = undefined;
//回調函數存儲器 主要解決異步處理流程
this.onReslovedCb = []; //成功回調
this.onRejectedCb = []; //失敗回調
//成功函數
let resolve = (value) => {
//只有在pending的時候才能夠調用
if (this.status == PENDING) {
this.value = value;
this.status = RESOLVED;
this.onReslovedCb.forEach(fn => fn())
}
}
//失敗函數
let reject = (reason) => {
//只有在pending的時候才能夠調用
if (this.status == PENDING) {
this.reason = reason;
this.status = REJECTED
this.onRejectedCb.forEach(fn => fn())
}
}
try {
//執行器 默認會當即執行
executor && executor(resolve, reject);
} catch (e) {
//執行的時候出現錯誤
reject(e)
}
}
}
複製代碼
關鍵點then 方法是Promise已經失敗/成功時候調用
先看下promise/A+的規範 粗略列舉了重要的內容編程
promise.then(onFulfilled, onRejected)
複製代碼
onFulfilled,onRejected
是then兩個參數
onFulfilled
不是函數,將會被直接忽略 onRejected
不是函數,也會被直接忽略 then
方法可以在同一個promise
上可以被調用屢次
fulfilled/onRejected
,全部的then回調都必須按照他們的調用初始順序執行其中onFulfilled
、onRejected
是咱們在外部調用的時候傳遞進行的回調函數;axios
then(onfulfilled, onrejected) {
//1. 參數是可選則的參數 須要進行判斷是否存在
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : data => data
onrejected = typeof onrejected == 'function' ? onrejected : error => {
throw error
}
// 2.若是存在回調函數,則執行內部的回調函數,裏面的函數會馬上執行
let promise2 = new Promise((resolve, reject) => {
//2.1 Promise 返回成功的時候
if (this.status == RESOLVED) {
// 定時器處理異常 爲了保障promise2已經用完了
setTimeout(() => {
//try 執行函數的時候會報錯 在then裏面的數據
try {
//x 須要判斷是不是promise和規整化
let x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
//2.1-失敗的時候
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onrejected && onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
// 2,1- pending 若是當前是pending 表示異步的請求還沒回來,進行收集內容
if (this.status == PENDING) {
//若是是異步 先訂閱好
this.onReslovedCb.push(() => {
//todo...
setTimeout(() => {
try {
let x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCb.push(() => {
//todo...
setTimeout(() => {
try {
let x = onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2;
}
複製代碼
then方法返回一個Promise的函數,注意在進行promise2的建立的時候,咱們在進行處理時候可能獲取的到的是underfined的promise2,所以須要開闢宏任務,promise2建立完成的時候在進行調用,而在進行處理的時候,咱們在onfulfilled、和onrejected獲得的參數可能不一樣,他們收到的參數可能爲幾種
api
.then(data=>{
return value;
},err=>{
return value
})
複製代碼
value
多是一個Promise
,須要執行當前的Promisevalue
字符串 value
爲使用者輸入,可能存在的值也是不肯定的,所以須要進行判斷,而onfulfilled、onRejected
調用後的結果也是不肯定的,所以須要進行類型的判斷;then的關鍵點在於resolvePromise
的函數,這個函數的作用究竟是什麼呢?數組
//判斷then裏面的函數返回值來進行判斷 x表示當前onreject
//promise都遵循的規範,所以須要進行兼容寫法
function resolvePromise(promise2, x, resolve, reject) {
//判斷當前的x是否是promise 是否是同一個 若是是同一個 就不要等待來了
if (promise2 === x) {
return reject(new TypeError("調用存在錯誤"))
}
//若是x是對象或者函數 判斷數據類型
/** * typeof 基本類型 * constructor * instanceof 判斷實例 * Object.toString */
if (typeof x === 'object' && typeof x !== null || typeof x == 'function') {
let called; //內部測試的時候,會成功和失敗都調用一下
try {
//取返回結果 then有可能經過defineProperty定義的
let then = x.then
//當前存在then方法 姑且是Promise
if (typeof then === 'function') {
//綁定this 到返回的x上,保證不用再次取then的值
then.call(x, y => {
if (called) return;
called = true; //防止屢次調用成功和失敗
//y可能仍是promise //採用promise的成功結果向下傳遞
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) return;
called = true;
reject(r) //採用失敗結果鄉下傳遞
}) //保證再次取到then的值
} else {
//說明x就是一個普通的對象 直接成功便可
resolve(x)
}
} catch (e) {
//promise 失敗 還能進行調用成功
//是一個普通的值 直接讓promise2成功便可
if (called) return;
called = true;
reject(e)
}
} else {
return resolve(x)
}
}
複製代碼
在then方法執行完後,Promise的實例狀態就會改變成resolved、或者reject
,此時then方法須要兼容一異步的調用類型.所以,當進入then函數後,若是當前的promise
的狀態仍然是Pending
,則表示當前結果尚未返回,所以須要增長onRejectedCb、onReslovedCb
用來存儲當前的執行函數,一旦某一個狀態改變,則進行調用該存儲列表中的數據,進行回調;promise
then
方法中的狀態收集和修改onfulfilled/onrejected
返回的參數類型不管Promise返回成功仍是失敗,都會執行該方法
finally()
方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做;finally
方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態究竟是fulfilled仍是rejected。這代表finally
方法裏面的操做,應該是與狀態無關的,不依賴於 Promise 的執行結果。所以能夠綁定此事件在當前promise實例的then方法上,在成功的時候回調傳入的函數,在失敗的時候也進行回調傳入的參數;
/** * finally 函數 promise m每次執行後都會進行執行 * @param {*} cb */
Promise.prototype.finally = function (cb) {
//finally 傳入函數,不管成功或者失敗都會執行
return this.then(data => {
//Promise.resolve 能夠等待這個promise完成
return Promise.resolve(cb().then(() => data))
}, err => {
//失敗的時候也執行
return Promise.reject(cb().then(() => {
throw err
}))
})
}
複製代碼
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。
//異常處理 用於指定發生錯誤時的回調函數。
//promise拋出一個錯誤,就被catch()方法指定的回調函數捕獲
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
複製代碼
Promise.all
可用於接收一個數組做爲參數,參數能夠不是數組,可是必須有Iterator接口,且返回的每一個成員都是Promise的實例,他的結果是根據傳入的數據進行變化的
const p = Promise.all([p1, p2, p3]);
複製代碼
/** * 所有成功才能成功,一個失敗纔會失敗 * promiseList 表示當前傳遞的數組對象 */
Promise.all = function (promiseList) {
return new Promise((resolve, reject) => {
let arr = [];
let index = 0;
//解決多個異步併發的問題 計數器
function proceessData(key, value) {
arr[key] = value;
if (++index == promiseList.length) {
resolve(arr)
}
}
for (let i = 0; i < promiseList.length; i++) {
let current = promiseList[i];
if (isPromise(current)) {
current.then((data) => {
proceessData(i, data)
}, (err) => {
console.log("data")
reject(err)
})
} else {
proceessData(i, current)
}
}
})
}
function isPromise(value) {
if ((typeof value === 'object' && value !== null) || typeof value === 'function') {
if (typeof value.then == 'function') {
return true
}
}
return false;
}
複製代碼
Promise.race()方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。從字面意義上而言,「管道」返回最早獲得的那個
const p = Promise.race([p1, p2, p3]);
複製代碼
/** * 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。 * @param {array} promiseList 傳遞的參數列表對象 */
Promise.race = function (promiseList) {
// console.log(promiseList)
//將values中的內容包裝成promise的
if (!Array.isArray(promiseList)) {
return Promise.resolve();
}
promiseList = promiseList.map(item => {
return !isPromise(item) ? Promise.resolve(item) : item;
});
// 有一個實例率先改變狀態則進行操做
return new Promise((resolve, reject) => {
promiseList.forEach((pro, index) => {
pro.then(res => {
resolve(res)
}, err => {
reject(err)
})
})
})
}
複製代碼
Promise.allSettled()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果,不論是fulfilled仍是rejected,包裝實例纔會結束。該方法由 ES2020 引入。
/** * 方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果, * 不論是fulfilled仍是rejected,包裝實例纔會結束 */
Promise.allSettled = function (promiseList) {
return new Promise((resolve, reject) => {
let index = 0;
let arr = [] ;
//用於記錄當前的promise的執行狀態
function recordRequest(key, value) {
index++;
arr[key] = value;
//選擇這種計數的方式,主要是考慮存在異步的流程,等待全部流程都執行完成後在結束
if (index == promiseList.length) {
resolve(arr)
}
}
for (let i = 0; i < promiseList.length; i++) {
current = promiseList[i]
if (isPromise(current)) {
current.then((data) => {
//每執行完成一個,就去增長記錄
recordRequest(i, {
status: 'resolve',
value: data
})
}, (err) => {
//失敗的promise也記錄
recordRequest(i, {
status: 'reject',
reason: err
})
})
} else {
recordRequest(i, {
status: '',
value: current
})
}
}
})
}
複製代碼
Promise.any()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。該方法目前是一個第三階段的提案 。
/** * Promise.any()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。該方法目前是一個第三階段的提案 。 * @param {*} promiseList promise的參數列表 */
Promise.any = function(promiseList){
promiseList = promiseList.map(item => {
return !isPromise(item) ? Promise.resolve(item) : item;
});
let index = 0;
let result=[]
return new Promise((resolve,reject)=>{
for (let i = 0; i < promiseList.length; i++) {
current = promiseList[i]
if (isPromise(current)) {
current.then((data) => {
resolve(data)
}, (err) => {
index++;
result.push(err)
if(index == promiseList.length){
reject(err);
}
})
}
}
})
}
複製代碼
有時須要將現有對象轉爲 Promise
對象,Promise.resolve()
方法就起到這個做用。會返回一個狀態爲Resolved狀態的promise
Promise.resolve(value),其中value的值包含好多種
參數的類型可能存在幾種狀況
/** * Promis.resolve 函數 * @param {*} values 傳遞進來的變量函數 */
Promise.resolve = function (values) {
//1.參數是一個 Promise 實例 將原封不動的返回
if (values instanceof Promise) {
return values;
}
return new Promise((resolve, reject) => {
//2.參數是一個含有then對象 具備then方法
//Promise.resolve()方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then()方法。
if (isPromise(values)) {
values.then(resolve, reject);
} else {
//3.參數不是具備then()方法的對象,或根本就不是對象 若是參數是一個原始值,或者是一個不具備then()方法的對象,則Promise.resolve()方法返回一個新的 Promise 對象,狀態爲resolved。
//4.參數不是具備then()方法的對象,或根本就不是對象
//5.不帶有任何參數
resolve(values)
}
})
}
複製代碼
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
/** * //Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。 * 參數爲values字符串 */
Promise.reject = function (values) {
return new Promise((resolve, reject) => {
reject(values)
})
}
複製代碼
實用場景: 不知道或者不想區分,函數f是同步函數仍是異步操做,可是想用 Promise 來處理它。由於這樣就能夠無論f是否包含異步操做,都用then方法指定下一步流程,用catch方法處理f拋出的錯誤。通常就會採用下面的寫法。
因爲Promise.try
爲全部操做提供了統一的處理機制,因此若是想用then方法管理流程,用Promise.try
包裝一下,能夠更好地管理異常。
Promise.try = function (fn, argumnts = null, ...args) {
if (typeof fn == 'function') {
//馬上執行fn函數並進行調用返回
return new Promise(resolve => resolve(fn.apply(argumnts, args)))
} else {
const err = new TypeError(`${typeof fn} ${fn} is not a function`);
return Promise.try(() => {
throw err
});
}
}
複製代碼
let p = new Promise()
自己同步執行p.then()
纔是異步的執行