Promise作為非同步程式設計方案callback的進階版,解決了callback回撥地域問題的同時,還增加了一些特性:比如all,race方法是的promise在處理非同步程式設計時更加的強大和靈活。
Promise特性
- 構造器需要一個引數executor(執行器)函式,executor同時需要resolve和reject兩個函式作為引數,resolve是將promise轉換為fulfilled(成功態)時呼叫的函式,reject是將promise轉換為rejected(失敗態)時呼叫的函式。
- Promise具有三種狀態:pending(預設態),fulfilled,rejected,只能由pending--->fulfilled或者pending--->rejected。fulfilled和rejected之間不能轉換
- promise的then函式具有兩個引數onFullfilled和onRejected兩個回撥函式,這兩個回撥函式會在promise確定了狀態之後進行對應的呼叫
- onFullfilled和onRejected兩個回撥函式會被新增進微任務佇列(延時呼叫,不過優先順序高於setTimeout巨集任務)
- executor裡面如果有setTimeout延時呼叫,則promise會暫時處於pending狀態,此時執行then函式時需要onFullfilledCallbacks和onRejectedCallbacks兩個回撥佇列用於分別快取對應的回撥函式
- then函式會返回一個新的promise作為下一步的鏈式呼叫
根據以上特性初步實現一個最初的promise,程式碼如下:
class Promise {
constructor(executor) {
this.status = 'pending' //預設狀態
this.value = undefined; //成功時儲存resolve傳過來的引數
this.reason = undefined; //失敗時儲存reject傳過來的原因
this.onFullfilledCallbacks = [];//成功時回撥佇列
this.onRejectedCallbacks = [];//失敗時回撥佇列
let resolve = (data) => {
this.status = 'fullfilled';
this.value = data;
this.onFullfilledCallbacks.forEach(fn => fn());
}
let reject = (err) => {
this.status = 'rejected';
this.reason = err;
this.onRejectedCallbacks.forEach(fn => fn());
}
try {
executor(resolve, reject);
} catch (err) {//執行器如果報錯直接轉為失敗態,並且將失敗原因傳出
reject(err);
}
}
then(onFullfilled, onRejected) {
let promise2;//返回新的promise
if(this.status === 'fullfilled') {//如果成功了
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolve(x);
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
}
if(this.status === 'rejected') {//如果成功了
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolve(x);//即使失敗了下一次then依然會走進成功的回撥函式
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
}
if(this.status === 'pending') {//pending狀態需要將對應的回撥快取起來
promise2 = new Promise((resolve, reject) => {
this.onFullfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolve(x);
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolve(x);//即使失敗了下一次then依然會走進成功的回撥函式
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
});
}
return promise2;
}
}
複製程式碼
then鏈式呼叫實現
按照promise A+ 規範,返回值x需要進行以下解析處理:
1. x 與 promise 相等
如果 promise 和 x 指向同一物件,以 TypeError 為據因拒絕執行 promise
2. x 為 Promise
如果 x 為 Promise ,則使 promise 接受 x 的狀態:
- 如果 x 處於等待態, promise 需保持為等待態直至 x 被執行或拒絕
- 如果 x 處於執行態,用相同的值執行 promise
- 如果 x 處於拒絕態,用相同的據因拒絕 promise
3. x 為物件或函式
- 把 x.then 賦值給 then
- 如果取 x.then 的值時丟擲錯誤 e ,則以 e 為據因拒絕 promise
- 如果 then 是函式,將 x 作為函式的作用域 this 呼叫之。傳遞兩個回撥函式作為引數,第一個引數叫做 resolvePromise ,第二個引數叫做 rejectPromise:
- 如果 resolvePromise 以值 y 為引數被呼叫,則執行 [[Resolve]](promise, y)
- 如果 rejectPromise 以據因 r 為引數被呼叫,則以據因 r 拒絕 promise
- 如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次,則優先採用首次呼叫並忽略剩下的呼叫
- 如果呼叫 then 方法丟擲了異常 e:
- 如果 resolvePromise 或 rejectPromise 已經被呼叫,則忽略之
- 否則以 e 為據因拒絕 promise
- 如果 then 不是函式,以 x 為引數執行 promise
4. 如果 x 不為物件或者函式
以 x 為引數執行 promise
resolvePromise程式碼實現
function resolvePromise(promise2, x, resolve, reject) {
let called = false;
if(promise2 === x) {// x 與 promise 相等
return reject(new TypeError('迴圈引用了')); //丟擲錯誤
}
if(x instanceof Promise) {
if(x.status === 'pending') {//如果 x 處於等待態
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject);// promise 需保持為等待態直至 x 被執行或拒絕
}, reject);
} else {//如果 x 處於執行態或者拒絕態
x.then(resolve, reject);
}
return;
}
if(x !== null && (typeof x === 'object' || typeof x === 'function')) {//x 為物件或函式
try {
let then = x.then;
if(typeof then === 'function') {//如果 then 是函式
then.call(x, function(y) {//resolvePromise以值 y 為引數被呼叫
if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
return; //則優先採用首次呼叫並忽略剩下的呼叫
}
called = true;
resolvePromise(promise2, y, resolve, reject);//執行 [[Resolve]](promise, y)
}, function(r) {//如果 rejectPromise
if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
return; //則優先採用首次呼叫並忽略剩下的呼叫
}
called = true;
reject(r);//以 r 為原因拒絕 promise
});
} else {//如果 then 不是函式
resolve(x);//以 x 為引數執行 promise
}
} catch (err) {//如果取 x.then 的值時丟擲錯誤
if(called) {//如果 resolvePromise 或 rejectPromise 已經被呼叫
return; //忽略之
}
called = true;
reject(err);//以 e 為原因拒絕 promise
}
} else {//如果 x 不為物件或者函式
resolve(x);//以 x 為引數執行 promise
}
}
複製程式碼
其他補充
1. Promise的then鏈式呼叫還有個特點,就是在中間then沒有傳入引數時可以依次傳遞資料,例如:
Promise.resolve('aaaa').then().then().then(function(data) {console.log(data)});
這個特性是因為onFullfilled和onRejected在沒有被傳入時會被賦予預設值,如下:
if(typeof onFullfilled !== 'function') {
onFullfilled = value => value;
}
if(typeof onRejected !== 'function') {
onRejected = err => {
throw err;
}
}
複製程式碼
2. Promise例項的catch方法類似不傳onFullfilled引數的then方法,實現如下:
catch(callback) {
return this.then(null, callback);
}
複製程式碼
3. Promise的靜態all方法能夠在傳入的所以promise成功後返或資料,其中有一個失敗則全部失敗。
static all(promises) {
let dataArr = [];
let len = promises.length;
let count = 0;//統計成功次數
return new Promise(function(resolve, reject) {
for (let i = 0;i < len;i++) {
p.then(function(data) {
dataArr[i] = data;
if(++count === len) {//所有promise成功
resolve(dataArr);
}
}, reject);
}
});
}
複製程式碼
4. Promise的靜態race方法使得傳入的所以promise中最先成功的那個結果返回,有一個成功就成功
static race(promises) {
let len = promises.length;
return new Promise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}
複製程式碼
完整的Promise實現
class Promise {
constructor(executor) {
this.status = 'pending' //預設狀態
this.value = undefined; //成功時儲存resolve傳過來的引數
this.reason = undefined; //失敗時儲存reject傳過來的原因
this.onFullfilledCallbacks = [];//成功時回撥佇列
this.onRejectedCallbacks = [];//失敗時回撥佇列
let resolve = (data) => {
this.status = 'fullfilled';
this.value = data;
this.onFullfilledCallbacks.forEach(fn => fn());
}
let reject = (err) => {
this.status = 'rejected';
this.reason = err;
this.onRejectedCallbacks.forEach(fn => fn());
}
try {
executor(resolve, reject);
} catch (err) {//執行器如果報錯直接轉為失敗態,並且將失敗原因傳出
reject(err);
}
}
then(onFullfilled, onRejected) {
let promise2;//返回新的promise
if(typeof onFullfilled !== 'function') {
onFullfilled = value => value;
}
if(typeof onRejected !== 'function') {
onRejected = err => {
throw err;
}
}
if(this.status === 'fullfilled') {//如果成功了
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
}
if(this.status === 'rejected') {//如果成功了
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);//即使失敗了下一次then依然會走進成功的回撥函式
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
}
if(this.status === 'pending') {//pending狀態需要將對應的回撥快取起來
promise2 = new Promise((resolve, reject) => {
this.onFullfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);//即使失敗了下一次then依然會走進成功的回撥函式
} catch (err) {//失敗將錯誤傳出
reject(err);
}
}, 10);
});
});
}
return promise2;
}
catch(callback) {
return this.then(null, callback);
}
static all(promises) {
let dataArr = [];
let len = promises.length;
let count = 0;//統計成功次數
return new Promise(function(resolve, reject) {
for (let i = 0;i < len;i++) {
p.then(function(data) {
dataArr[i] = data;
if(++count === len) {//所有promise成功
resolve(dataArr);
}
}, reject);
}
});
}
static race(promises) {
let len = promises.length;
return new Promise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}
static deferred() {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
}
function resolvePromise(promise2, x, resolve, reject) {
let called = false;
if(promise2 === x) {// x 與 promise 相等
return reject(new TypeError('迴圈引用了')); //丟擲錯誤
}
if(x instanceof Promise) {
if(x.status === 'pending') {//如果 x 處於等待態
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject);// promise 需保持為等待態直至 x 被執行或拒絕
}, reject);
} else {//如果 x 處於執行態或者拒絕態
x.then(resolve, reject);
}
return;
}
if(x !== null && (typeof x === 'object' || typeof x === 'function')) {//x 為物件或函式
try {
let then = x.then;
if(typeof then === 'function') {//如果 then 是函式
then.call(x, function(y) {//resolvePromise以值 y 為引數被呼叫
if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
return; //則優先採用首次呼叫並忽略剩下的呼叫
}
called = true;
resolvePromise(promise2, y, resolve, reject);//執行 [[Resolve]](promise, y)
}, function(r) {//如果 rejectPromise
if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
return; //則優先採用首次呼叫並忽略剩下的呼叫
}
called = true;
reject(r);//以 r 為原因拒絕 promise
});
} else {//如果 then 不是函式
resolve(x);//以 x 為引數執行 promise
}
} catch (err) {//如果取 x.then 的值時丟擲錯誤
if(called) {//如果 resolvePromise 或 rejectPromise 已經被呼叫
return; //忽略之
}
called = true;
reject(err);//以 e 為原因拒絕 promise
}
} else {//如果 x 不為物件或者函式
resolve(x);//以 x 為引數執行 promise
}
}
複製程式碼