前端面試的時候,經常能看到這樣一道題,實現一個Promise
。
這篇文章將一步步實現 Promise,徹底弄懂 Promise。
Promise 基本構成
平時使用 Promise 我們可以知道 Promise 存在三種狀態 Pending、Resolve、Reject,在 new Promise
時需要傳入一個函數, 參數爲 resolve
和 reject
的函數,這兩個函數用來改變 Promise 的狀態。
最重要的還有個 then
的方法,then
函數可以傳入兩個函數作爲參數,第一個函數用來獲取異步操作的結果,第二個函數用來獲取錯誤的原因。
除此之外還需要 value
和 reason
存放 Promise 的結果或錯誤原因。
從上面這些信息可以轉化爲下面的代碼:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = null;
this.reason = null;
function resolve (value) {
this.status = RESOLVED;
this.value = value;
};
function reject (reason) {
this.status = REJECTED;
this.reason = reason;
};
executor(resolve.bind(this), reject.bind(this));
}
then(onFulfilled, onRejected) {
if (this.status === RESOLVED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
Promise 的狀態只允許修改一次,那麼 resolve
和 reject
需要加上狀態判斷。
function resolve (value) {
if (this.status !== PENDING) return;
this.status = RESOLVED;
this.value = value;
};
function reject (reason) {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
};
在調用 then
函數時,Promise 的狀態有可能還是 Pending 的狀態,這時需要將 then
函數的兩個參數進行保存,狀態改變時在進行調用。then
函數有可能會調用多次,那麼可以用數組保存參數。
class Promise {
constructor(executor) {
// ...
this.resolveCbs = [];
this.rejectCbs = [];
function resolve (value) {
// ...
this.resolveCbs.map(fn => fn(this.value));
};
function reject (reason) {
// ...
this.rejectCbs.map(fn => fn(this.reason));
};
}
then(onFulfilled, onRejected) {
// ...
if (this.status === PENDING) {
this.resolveCbs.push(onFulfilled);
this.rejectCbs.push(onRejected);
}
}
}
寫到這裏,一個最基本的 Promise 就可以使用了。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 500);
}).then(res => {
console.log(res);
});
上面的代碼雖然完成了最基本的 Promise,但是還未實現 then
函數的鏈式調用。
實現鏈式調用
new Promise((resolve, reject) => {
// ...
}).then(res => {
// ...
}).then(res => {
// ...
})
鏈式調用也是 Promise 的重點所在,因爲有了鏈式調用,才能避免回調地獄的問題。接下來就來一步步實現。
then
是 Promise 的方法,爲了能夠繼續調用 then
函數,需要 then
函數返回一個新的 Promise。
onFulfilled
或 onRejected
的返回值有可能也是一個 Promise,那麼需要等待 Promise 執行完的結果傳遞給下一個 then
函數。如果返回的不是 Promise,就可以將結果傳遞給下一個 then
函數。
將 then
函數進行如下修改,resolvePromise 另外實現。
class Promise {
// ...
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}
if (this.status === REJECTED) {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
}
if (this.status === PENDING) {
this.resolveCbs.push(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
});
this.rejectCbs.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
});
}
});
return promise2;
}
}
實現 resolvePromise
then(onFulfilled, onRejected) {
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
// 不允許 promise2 === x; 避免自己等待自己
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止重複調用
let called = false;
try {
if (x instanceof Promise) {
let then = x.then;
// 第一個參數指定調用對象
// 第二個參數爲成功的回調,將結果作爲 resolvePromise 的參數進行遞歸
// 第三個參數爲失敗的回調
then.call(x, y => {
if (called) return;
called = true;
// resolve 的結果依舊是 Promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
});
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
}
// ...
}
優化 then
函數
then
函數的 onFulfilled
和 onRejected
參數允許不傳.
Promise/A+ 規範要求 onFulfilled
和 onRejected
不能被同步調用,可以使用 setTimeout
改爲異步調用。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => { return v };
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
function resolvePromise (promise2, x, resolve, reject) {...}
let promise2 = new Promise((resolve, reject) => {
function fulfilled () {
setTimeout(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}, 0);
};
function rejected () {
setTimeout(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
}, 0);
}
if (this.status === RESOLVED) {
fulfilled.call(this);
}
if (this.status === REJECTED) {
rejected.call(this);
}
if (this.status === PENDING) {
this.resolveCbs.push(fulfilled.bind(this));
this.rejectCbs.push(rejected.bind(this));
}
});
return promise2;
}
catch 等方法實現
class Promise {
// ...
catch(fn) {
this.then(null, fn);
}
static resolve (val) {
return new Promise((resolve) => {
resolve(val);
});
}
static reject (val) {
return new Promise((resolve, reject) => {
reject(val);
});
}
static race(promises) {
return new Promise((resolve, reject) => {
promises.map(promise => {
promise.then(resolve, reject);
});
});
}
static all(promises) {
let arr = [];
let i = 0;
return new Promise((resolve, reject) => {
promises.map((promise, index) => {
promise.then(data => {
arr[index] = data;
if (++i === promises.length) {
resolve(arr);
}
}, reject);
})
})
}
}
參考文章
BAT前端經典面試問題:史上最最最詳細的手寫Promise教程
如果你喜歡我的文章,希望可以關注一下我的公衆號【前端develop】