Promise是當前ES6語法中的異步解決方案,本文從基本概念開始,層層分析,一步一步手寫實現Promise,但願能和你們一塊兒完全掌握Promise。 javascript
Promise是異步編程的一種解決方案,跟傳統回調函數來實現異步相比,其設計更合理,代碼更易懂。Promise是"美顏後"的異步解決方案.
在ES6中, 其寫進了語言標準提供原生支持。
Promise有兩大特色:html
pending
, fulfilled
, rejected
上圖摘自MDNjava
Promise的具體用法能夠參考阮一峯老師的 《ECMAScript 6 入門》或MDN。
這裏有幾個點須要注意:git
new Promise((resolve, reject) => {
resolve(1);//爲了防止輸出2,改爲return resolve,平常編碼中也要養成return resolve的習慣
console.log(2);// 能夠輸出2
}).then(r => {
console.log(r);
});
複製代碼
reject=>{}
能夠省略,建議使用 catch(error=>{})
,其不只可以獲取reject的內容,還能夠捕獲到Promise函數和then函數中產生的異常。Promise內部的異常會被吃掉,不會被外部的異常捕獲。若是熟悉了Promise的使用,其實咱們知道,Promise提供了異步編程的語法糖,使原來異步回調的操做能夠用同步的方式來表達。es6
在傳統AJAX異步解決方案中,咱們通常使用回調函數來解決數據的接收和處理,以下:github
$.get(url, (data) => {
console.log(data)
)
複製代碼
在某些需求場景下,咱們須要發送多個異步請求,而且每一個請求之間結果之間須要相互依賴,隨着回調函數相互嵌套的增長,函數之間的邏輯就會耦合在一塊兒,難以維護,造成回調地獄。以下所示:面試
let country = 'china';
let city = 'shanghai';
let district = 'PD'
$.get(`xxxxx/countries`,countries=>{
/** **這裏能夠再第一個select控件中,渲染國家列表, **/
countries.forEach((item)=>{
if(item===country){
//查找並選中當前國家
$.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
/** **這裏能夠再第二個select控件中,渲染城市列表, **/
cities.forEach((item)=>{
if(item === city){
//查找並選中當前城市
$.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
/** **這裏能夠再第三個select控件中,渲染地區列表, **/
dists.forEach(item=>{
if(item==district){
//查找並選中地區
}
})
})
}
})
})
}
});
});
複製代碼
上述是一個簡單的三級聯動功能,使用三個回調函數。它們相互嵌套邏輯複雜,耦合嚴重。
Promise解決了回調地獄問題,經過Promise將上述代碼改寫成編程
let country = 'china';
let city = 'shanghai';
let district = 'PD'
new Promise(() => {
$.get(`xxxxx/countries`, countries => {
return countries;
});
}).then((countries) => {
countries.forEach((item) => {
if (item === country) {
$.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
return cities;
})
}
})
}).then((cities) => {
cities.forEach((item) => {
if (item === city) {
$.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
return dists;
})
}
})
}).then((dists) => {
dists.forEach(item => {
if (item == district) {
//查找並選中地區
}
})
})
複製代碼
此時,將異步執行由原來的回調,改爲了 then...then....then...
鏈式調用的方式。
線性的鏈式執行(同步)更符合人類的思考習慣(更直白的說,按照順序一步一步的閉環符合人類思考習慣。Promise就是將本來異步回調的語法形式,改寫成同步。因此實現Promise,就是把異步回調這種醜陋的方式改爲鏈式調用。經過手寫Promise,咱們來理解和消化其設計思想。 數組
有了上述的鋪墊,咱們瞭解Promise的概念和特徵,也知道了Promise的優點,下面咱們一步步來實現Promise。promise
function Promise(executor){
try{
executor()
}catch(e){
console.log(e):
}
}
複製代碼
function Promise(executor){
function resolve(value){
//能夠將executor中的數據傳入resolve中
}
function reject(value){
//能夠將executor中的數據傳入reject中
}
try{
executor(resolve,reject)
}catch(e){
console.log(e):
}
}
複製代碼
PENDING=>FULFILLED
,在執行reject時,由 PENDING=>REJECTED
。const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise(executor){
var that = this;
that.status = pending;
that.value = null;
that.error = null;
function resolve(val){
//當且僅當PENDING=》FULFILLED
if(that.status === pending){
that.status = fulfilled;
that.value = val;
}
}
function reject(val){
//當且僅當PENDING=》REJECTED
if(that.status === pending){
that.status = rejecting;
that.error = val;
}
}
try{
executor(resolve,reject);
}catch(e){
//在executor中產生的異常在reject中能夠捕獲。可是reject的異常,智能catch捕獲
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
var that = this;
if(that.status === fulfilled){
//當狀態改變後,執行then中的回調
onFulfilled(that.value);
}
if(that.status === rejecting){
//同上
onRejected(that.error)
}
};
複製代碼
執行以下代碼
new Promise((resolve)=>{
resolve(1);
}).then((res)=>{
console.log(res);
});
複製代碼
打印結果以下
若是executor函數存在異步,則須要等待resolve或者reject回調執行纔會執行then中的函數體。
此處使用回調來解決異步:
第一步:定義兩個Promise的成員:onResolvedCallBack和onRejectCallBack,存儲then函數中的入參。
第二步:當執行then函數時,若是當前狀態仍是PENDING(此時executor內部有異步操做)那麼就將then中的參數傳入onResolvedCallBack和onRejectCallBack中。若是此時狀態是非PENDING,那麼直接執行傳入的函數便可。
第三步:Promise中的resolve函數執行時,觸發onResolvedCallBack或者onRejectCallBack中的函數。
具體代碼以下:
const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise1(executor) {
var that = this;
that.status = pending;
that.value = null;
that.error = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(val) {
if (that.status === pending) {
that.status = fulfilled;
that.value = val;
that.resolvedCallbacks.map(cb => cb(that.value));
}
}
function reject(val) {
if (that.status === pending) {
that.status = rejecting;
that.error = val;
that.rejectedCallbacks.map(cb => cb(that.value));
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise1.prototype.then = function (onFulfilled, onRejected) {
var that = this;
//爲了保證兼容性,then的參數只能是函數,若是不是要防止then的穿透問題
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if (that.status === pending) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.status === fulfilled) {
onFulfilled(that.value);
}
if (that.status === rejecting) {
onRejected(that.error)
}
};
複製代碼
執行以下一段代碼:
let p = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 3000)
});
p.then((res) => { console.log(res); });
console.log(2);
複製代碼
打印結果以下:
let p = new Promise((resolve) => {
resolve(1);
});
p.then((res) => { console.log(res); });
console.log(2);
複製代碼
此處咱們打印結果爲
microtask和macrotask是Event Loop的知識點,關於Event Loop能夠參考阮一峯老師的《JavaScript 運行機制詳解:再談Event Loop》
此處咱們使用setTimeout來模擬then的microtasks(注:)
function resolve(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
function reject(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
複製代碼
咱們執行以下代碼:
let p = new Promise((resolve) => {
var a = new Promise((resolve) => { resolve(1) });
resolve(a);
});
p.then((res) => {
console.log(res);
});
複製代碼
此處resolve傳入的是Promise對象,打印結果爲:
function resolve(value) {
if (val instanceof Promise) {
return val.then(resolve, reject);
}
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
複製代碼
在Promise中,在then中執行return語句,返回的必定是Promise對象,這也是then可以鏈式調用的緣由。
首先咱們將then中的以下片斷
if (that.status === pending) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
複製代碼
變形
if (that.status === pending) {
that.resolvedCallbacks.push(()=>{onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{onRejected(that.value)});
}
複製代碼
它們之間只是寫法的差別,效果相同。
由於咱們須要對then裏傳入的函數onFulfilled, onRejected返回的值進行判斷,因此咱們須要對then繼續改寫
if (that.status === pending) {
that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{const x = onRejected(that.value)});
}
複製代碼
由於then返回的是Promise,因此繼續完善
if (that.status === pending) {
return new Promise(resolve,reject){
that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{const x = onRejected(that.error)});
}
}
複製代碼
執行onFulfilled和onRejected時,使用try...catch...,因此繼續完善
let promise2 = null;
if (that.status === pending) {
return promise2 = new Promise((resolve,reject)=>{
that.resolvedCallbacks.push(()=>{
try{
const x = onFulfilled(that.value);
}catch(e){
reject(e);
}
});
that.rejectedCallbacks.push(()=>{
try{
const x = onRejected(that.error);
}catch(e){
reject(e);
}
});
});
}
複製代碼
上述x是onFulfilled(that.value)和onRejected(that.error)的返回值,爲了保證then能夠鏈式調用,也就是promise2的resolve可以resolve一個Promise對象,可是x返回的多是Promise對象,多是值,也多是函數,那麼此處須要對x進行適配一下。此時引入resolvePromise函數,實現以下:
/** * 對resolve 進行改造加強 針對x不一樣值狀況 進行處理 * @param {promise} promise2 promise1.then方法返回的新的promise對象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
// 若是從onFulfilled中返回的x 就是promise2 就會致使循環引用報錯
return reject(new TypeError('循環引用'));
}
let called = false; // 避免屢次調用
// 若是x是一個promise對象 (該判斷和下面 判斷是否是thenable對象重複 因此無關緊要)
if (x instanceof Promise) { // 得到它的終值 繼續resolve
if (x.status === PENDING) { // 若是爲等待態需等待直至 x 被執行或拒絕 並解析y值
x.then(y => {
resolvePromise(promise2, y, resolve, reject);
}, reason => {
reject(reason);
});
} else {
// 若是 x 已經處於執行態/拒絕態(值已經被解析爲普通值),用相同的值執行傳遞下去
x.then(resolve, reject);
}
// 若是 x 爲對象或者函數
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try { // 是不是thenable對象(具備then方法的對象/函數)
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, reason => {
if(called) return;
called = true;
reject(reason);
})
} else { // 說明是一個普通對象/函數
resolve(x);
}
} catch(e) {
if(called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
複製代碼
此時that.status === pending的代碼塊也要繼續修改
if (that.status === pending) {
return promise = new Promise1((resolve, reject) => {
that.resolvedCallbacks.push(() => {
try {
const x = onFulfilled(that.value);
resolvePromise(promise,x,resolve,reject);
} catch (e) {
reject(e);
}
});
that.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.error);
} catch (e) {
reject(e);
}
});
})
}
複製代碼
同理that.status===fulfilled和that.status===rejecting的時候代碼以下:
if (that.status === FULFILLED) { // 成功態
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
})
}
if (that.status === REJECTED) {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
複製代碼
Promise.all的用法以下
const p = Promise.all([p1, p2, p3]).then((resolve)=>{},(reject)=>{});
複製代碼
Promise.all方法接受一個數組做爲參數,只有當數組的全部Promise對象的狀態所有fulfilled,纔會執行後續的then方法。
根據all的用法和特色,咱們推測Promise.all返回一個Promise對象,在Promise對象中去等待Promise數組對象的函數執行完畢,數組中的每一個對象執行完畢都+1,當等於數組的長度時,resolve數組對象中全部resolve出來的值。
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let done = gen(promises.length, resolve);
promises.forEach((promise, index) => {
promise.then((value) => {
done(index, value)
//每執行一次都會往values數組中放入promise對象數組成員resolve的值
//當values的長度等於promise對象數組的長度時,resolve這個數組values
}, reject)
})
})
}
//使用閉包,count和values在函數的聲明週期中都存在
function gen(length, resolve) {
let count = 0;
let values = [];
return function(i, value) {
values[i] = value;
if (++count === length) {
console.log(values);
resolve(values);
}
}
}
複製代碼
Promise.race的用法和all相似,區別就是promise對象數組其中有一個fulfilled,就執行then方法,實現以下
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
}
複製代碼
須要注意的是all和race函數中的數組成員不必定是Promise對象,若是不是Promise提供了resolve方法將其轉化成Promise對象。resolve的實現很簡單以下:
Promise.resolve = function (value) {
return new Promise(resolve => {
resolve(value);
});
}
複製代碼
《ES6入門-阮一峯》
《ES6 系列之咱們來聊聊 Promise》
《異步解決方案----Promise與Await》
《Promise原理講解 && 實現一個Promise對象》
《面試精選之Promise》
《Promise/A+》
《Promise之你看得懂的Promise》
《八段代碼完全掌握 Promise》
《Promise不會??看這裏!!!史上最通俗易懂的Promise!!!》
《Promise 必知必會(十道題)》