Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一。git
Promise/A+ 是 Promise 最小的一個規範。包括es6
- Promise 狀態
- then 方法
- Promise 解析過層
複製代碼
只有一個then
方法, 沒有catch
、race
、all
等方法 ECMAscript 6 Promise 是符合Promise/A+ 標準之一。github
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
console.log(data);
}, err => {
console.log('err', err)
})
// 200
複製代碼
分析一下:編程
1. new Promise 返回一個promise對象, 接受一個`executor`執行器函數, 當即調用函數。
2. `executor` 接收兩個參數`resolve`、`reject`, 同時這兩個參數也是函數。
3. Promise 實例具備狀態, 默認`pending`(等待), 執行器調用resolve後,實例狀態變爲`resolved`(成功)。 執行器調用reject,實例狀態變爲`rejected`(失敗)。
4. Promise 實例狀態一經改變, 將不能再修改。
5. promise 實例 都有 `then`方法。then 方法中有兩個參數。`onResolved`成功回調函數, `onRejected`失敗回調函數
6. 執行器`executor`調用`resolve`後, then中`onResolved`將會執行, 當執行器`executor`調用`reject`後, then方法第二個參數`onRejected`將會執行。
複製代碼
實現一下:數組
// promise 三個狀態
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';
function PromiseA (executor) {
// 保存一下this, 防止this出現指向不明
var _this = this;
// 初始化 promise 的值
_this.data = undefined;
// 初始化 promise 的狀態
_this.status = PENDING;
function resolve(value) {
// 在pending時修改對應狀態, 和 promise 值
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
}
}
function reject(reason) {
// 在pending時修改對應狀態, 和 promise 值
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
}
}
executor(resolve, reject);
}
PromiseA.prototype.then = function(onResolved, onRejected) {
var _this = this;
// 狀態是成功狀態, 當即執行成功回調, 並傳入其值
if(_this.status === RESOLVED) {
onResolved(_this.data);
}
// 狀態是失敗狀態, 當即執行失敗回調, 並傳入其值
if(_this.status === REJECTED) {
onRejected(_this.data);
}
}
module.exports = PromiseA;
複製代碼
let p = new PromiseTest(function(resolve, reject) {
setTimeout(() => {
resolve('200');
}, 1000)
})
p.then(data => {
console.log(1, data)
}, err => {
console.log('err', err)
})
p.then(data => {
console.log(2, data)
}, err => {
console.log('err', err)
})
// 1, '200'
// 2, '200'
複製代碼
分析一下:promise
結果將會在一秒中以後打印, 即then
方法的失敗和成功回調是在promise 的異步執行完以後才觸發的, 因此 在調用then
方法的時候 promise 的狀態並非成功或者失敗, 先將成功或者失敗的回調函數保存起來,等異步執行完成後再執行對應的成功或者失敗回調函數。 then 方法能夠調用屢次, 因此保存時須要使用數組框架
實現一下:異步
function PromiseA (executor) {
// ...
// 保存成功和失敗的回調函數
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
// 調用成功函數
function resolve(value) {
// 在pending時修改對應狀態, 和 promise 值
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
_this.resolvedCallbacks.forEach(function(fn) {
fn();
});
}
}
// 調用失敗函數
function reject(reason) {
// 在pending時修改對應狀態, 和 promise 值
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
_this.rejectedCallbacks.forEach(function(fn) {
fn();
});
}
}
}
PromiseA.prototype.then = function(onResolved, onRejected) {
// ...
// 狀態是等待, 將回調函數保存起來
if(_this.status === PENDING) {
_this.resolvedCallbacks.push(function() {
onResolved(_this.data);
})
_this.rejectedCallbacks.push(function() {
onRejected(_this.data);
})
}
}
複製代碼
let p = new PromiseA((resolve, reject) => {throw new Error('error')});
p.then(data => {
console.log(data);
}, err => {
console.log('err', err)
})
// err Error: error
複製代碼
分析一下: Promise 出錯時會直接改變到失敗狀態, 並將失敗緣由傳遞過去。 直接對執行函數executor
進行異常處理, 出錯就進入reject
函數。異步編程
實現一下:函數
function PromiseA (executor) {
// ...
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
複製代碼
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
console.log(1, data)
throw Error('oooo')
}, err => {
console.log('1err', err)
}).then(data => {
console.log(2, data);
}, err => {
console.log('2err', err)
}).then(data => {
console.log(3, data)
}, err => {
console.log('3err', err)
})
console.log('start');
// start
// 1 '200'
// 2err Error: oooo
// 3 undefined
複製代碼
分析一下:
start
, 使用setTimeout
保證執行順序。then
方法後, 返回了一個新的Promise實例,then
方法 onResolved
或onRejected
回調的參數。then
時, 當任何一個then
執行出錯時, 鏈時調用的下一個then
時會執行錯誤的回調,undefined
, 再次調用會執行成功的回調, 即上面的 3 undefined
。實現一下:
function PromiseA (executor) {
// ...
function resolve(value) {
// 在pending時修改對應狀態, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
_this.resolvedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
function reject(reason) {
// 在pending時修改對應狀態, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
_this.rejectedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
}
PromiseA.prototype.then = function(onResolved, onRejected) {
var _this = this;
var promise2;
promise2 = new PromiseA(function(resolve, reject) {
// 異步執行, 保證調用順序
setTimeout(function() {
// 狀態是成功狀態, 當即執行成功回調, 並傳入其值
if(_this.status === RESOLVED) {
// then方法執行 異常處理, 錯誤進入執行reject
try {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason)
}
}
// 狀態是失敗狀態, 當即執行失敗回調, 並傳入其值
if(_this.status === REJECTED) {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
}
// 狀態是等待, 將回調函數保存起來
if(_this.status === PENDING) {
_this.resolvedCallbacks.push(function() {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
})
_this.rejectedCallbacks.push(function() {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
})
}
})
})
return promise2;
}
function resolvePromise(promise2, x, resolve, reject) {
resolve(x);
}
複製代碼
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then()
.then(null, err => {
console.log('1err', err)
})
.then(data => {
console.log(data)
}, err => {
console.log('2err', err)
})
// '200'
複製代碼
分析一下: 當上一個then
沒有傳遞迴調參數, 或者參數爲null
時, 須要將值傳遞給下一個then
方法 then
方法的兩個參數都是可選參數onResolved
和 onRejected
, 故, 判斷回調函數是否爲函數, 就把then
的參數留空而且讓值穿透到後面。
實現一下:
PromiseA.prototype.then = function(onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
onRejected = typeof onRejected === 'function' ? onREjected : function(reason) {throw reason};
// ...
}
複製代碼
let p = new Promise(function(resolve, reject) {
resolve('200');
})
var p2 = p.then(() => {
return p2;
})
p2.then(() => {
console.log(1)
}, err => {
console.log('err1', err)
})
// err1 TypeError: Chaining cycle detected for promise
複製代碼
分析一下: 上述代碼, 讓 p 的then
方法回調本身, 就會產生循環回調, 故, then
方法中的回調函數不能是本身自己
實現一下:
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new TypeError('循環引用'));
}
}
// ...
複製代碼
then
後返回的新 Promise 實例稱爲 promise2,將 then
回調返回的值稱爲x
x
爲同一個對象, 本身等待本身, 循環引用。x
是一個對象或者函數且不是null
,就去取 x
的 then 方法,若是 x
是對象,防止x
是經過 Object.defineProperty
添加 then
屬性,並添加 get 和 set 監聽,若是在監聽中拋出異常,須要被捕獲到,x.then
是一個函數,就看成 x
是一個 Promise 實例,直接執行x
的 then
方法,執行成功就讓 promise2
成功,執行失敗就讓promise2
失敗,若是 x.then
不是函數,則說明 x
爲普通值,直接調用promise2
的 resolve
方法將 x
傳入,不知足條件說明該返回值就是一個普通值,直接執行 promise2
的 resolve
並將 x 做爲參數傳入;x
的 then
方法,回調中傳入的參數仍是一個 Promise 實例,循環往復,須要遞歸 resolvePromise
進行解析resolve
和 reject
的狀況,應該聲明一個標識變量 called
作判斷來避免這種狀況實現一下:
function resolvePromise(promise2, x, resolve, reject) {
var then;
// 爲了不屢次調用
var called = false;
if(promise2 === x) {
return reject(new TypeError('循環回調'));
}
// x 若是是普通值(非 object 或 function), 直接resolve
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
// 每一個promise 都會有then方法, 使用_then保存, 防止出錯, 使用try catch
try {
then = x.then;
if(typeof then === 'function') {
// 肯定 this 指向x
then.call(x, function(y) {
if(called) return;
called = true;
return resolvePromise(promise2, y, resolve, reject);
}, function(e) {
if(called) return;
called = true;
return reject(e);
})
} else {
resolve(x);
}
} catch (err) {
if(called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
複製代碼
使用 這個包promises-aplus-tests 走下單元測試 按其說明,須要提供一個這樣的類靜態函數:
PromiseA.deferred = PromiseA.defer = function() {
var dfd = {}
dfd.promise = new PromiseA(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
複製代碼
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
throw new Error('eeee');
}, err => {
console.log('err', err)
}).catch(err => {
console.log(err)
})
// Error eeee
複製代碼
實現一下:
PromiseA.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
複製代碼
let p = Promise.resolve('200');
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// 200
複製代碼
實現一下:
Promise.prototype.resolve = function(value) {
return new Promise(function(resolve, reject) {
resolve(value);
})
}
複製代碼
let p = Promise.reject('eeee');
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// err eeee
複製代碼
實現一下:
Promise.reject = function(reason) {
return new Promise(function(resolve, reject) {
reject(reason)
})
}
複製代碼
let p1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1)
}, 1000)
})
let p2 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(2)
}, 2000)
})
let p3 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(3)
}, 3000)
})
let p = Promise.race([p1, p2, p3])
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// 1
複製代碼
實現一下:
Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
promises.forEach(function(promise) {
promise.then(resolve, reject)
})
})
}
複製代碼
// promise 三個狀態
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';
/** * * @param {function} executor */
function PromiseA (executor) {
// 保存一下this, 防止this出現指向不明
var _this = this;
// 初始化 promise 的值
_this.data = undefined;
// 初始化 promise 的狀態
_this.status = PENDING;
// 保存成功和失敗的回調函數
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
// 調用成功函數
function resolve(value) {
// 在pending時修改對應狀態, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
_this.resolvedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
// 調用失敗函數
function reject(reason) {
// 在pending時修改對應狀態, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
_this.rejectedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
// 用於處理 new PromiseA((resolve, reject) => {throw new Error('error')})
try {
executor(resolve, reject);
} catch (reason) {
reject(reason)
}
}
/** * * @param {promise} promise2 then 執行後返回 新的Promise對象 * @param {*} x promise中onResolved 的返回值 * @param {*} resolve promise2的resolve方法 * @param {*} reject promise2的reject方法 */
function resolvePromise(promise2, x, resolve, reject) {
var then;
// 爲了不屢次調用
var called = false;
if(promise2 === x) {
return reject(new TypeError('循環回調'));
}
// x 若是是普通值(非 object 或 function), 直接resolve
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
// 每一個promise 都會有then方法, 使用_then保存, 防止出錯, 使用try catch
try {
then = x.then;
if(typeof then === 'function') {
// 肯定 this 指向x
then.call(x, function(y) {
if(called) return;
called = true;
return resolvePromise(promise2, y, resolve, reject);
}, function(e) {
if(called) return;
called = true;
return reject(e);
})
} else {
resolve(x);
}
} catch (err) {
if(called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
/** * @param {function} onResolved 成功回調 * @param {function} onRejected 失敗回調 */
PromiseA.prototype.then = function(onResolved, onRejected) {
var _this = this;
var promise2;
// 值的穿透
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason};
return promise2 = new PromiseA(function(resolve, reject) {
// 異步執行, 保證調用順序
setTimeout(function() {
// 狀態是成功狀態, 當即執行成功回調, 並傳入其值
if(_this.status === RESOLVED) {
// 針對內部
try {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch(reason) {
reject(reason);
}
}
// 狀態是失敗狀態, 當即執行失敗回調, 並傳入其值
if(_this.status === REJECTED) {
try {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}
// 狀態是等待, 將回調函數保存起來
if(_this.status === PENDING) {
_this.resolvedCallbacks.push(function() {
try {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
})
_this.rejectedCallbacks.push(function() {
try {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason)
}
})
}
})
})
}
PromiseA.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
PromiseA.resolve = function(value) {
return new PromiseA(function(resolve, reject) {
resolve(value);
})
}
PromiseA.reject = function(reason) {
return new PromiseA(function(resolve, reject) {
reject(reason)
})
}
PromiseA.race = function(promises) {
return new PromiseA(function(resolve, reject) {
promises.forEach(function(promise) {
promise.then(resolve, reject)
})
})
}
// 配合使用 promises-aplus-tests 測試
PromiseA.deferred = PromiseA.defer = function() {
var dfd = {}
dfd.promise = new PromiseA(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = PromiseA;
複製代碼