前言
從事前端的朋友或多或少的接觸過Promise
,當程式碼中回撥函式層級過多你就會發現Promise
非同步程式設計的魅力,相信此文一定能幫你排憂解惑
Promise概念
Promise
是JS
非同步程式設計中的重要概念,非同步抽象處理物件,是目前比較流行Javascript
非同步程式設計解決方案之一
或許是筆者理解能力有限,對官方術語怎麼也感受不到親切,下面我來用通俗易懂的語言解釋下:
Promise
是一個包含三種狀態的物件(pending
、fulfilled
、rejected
),可以鏈式的處理非同步請求(then
方法)並能很好地處理異常問題,
是解決回撥地獄的良好方案之一。
回撥函式處理多層非同步示例
$.ajax({
url: url1,
success: function(rsp){
$.ajax({
url: url2,
success: function(rsp){
$.ajax({
url: url3,
success: function(rsp){
//do sth
},
error: function(error){
}
});
},
error: function(error){
}
});
},
error: function(error){
}
});
複製程式碼
將promise
封裝在$.ajax
中
$.ajax = function(config){
return new Promise(function(resolve, reject){
//1省略...
xmlhttp.onreadystatechange = function(){
if(xmlhttp.status==200){
resolve(rspData);
}else{
reject(xmlhttp.statusText);
}
};
//2省略...
})
}
$.ajax({url: url1}).then(function(val){
return $.ajax({url: val.url})
}).then(function(val){
return $.ajax({url: val.url})
}).catch(function(err){
console.log(err);
}}
複製程式碼
封裝好的Promise
處理非同步可讀性可維護性以及程式碼美觀度不言而喻
Promise API
'new' Promise
//pending狀態的promise
var promise = new Promise(function(resolve, reject){
//do sth
})
//fulfilled狀態的promise
var promise = Promise.resolve(1)
.then(function resolve(value){console.log(value)});
// var promise = new Promise(function(resolve){resolve(1)})
//rejected狀態的promise
var promise = Promise.reject(new Error('error'))
.catch(function(error){console.error(error)});
// var promise = new Promise(function(resolve,reject){resolve(new Error('error'))})
複製程式碼
Promise.prototype.then
Promise#then
promise.then(onFulfilled, onRejected)
複製程式碼
返回一個新的promise
這裡經常會有一個疑問:為什麼不返回原來的promise
,個人是這樣認為的,若返回同一個promise
則狀態不一致,promise規範說明當pending
至fulfilled
/ rejected
時狀態確定後不能再改變。
Promise.prototype.catch
Promise#catch
promise.catch(function(error){ throw new Error(error); })
複製程式碼
注意:IE8
及以下版本會出現 identifier not found
的語法錯誤,可將點標記法改為中括號標記法
promise['catch'](function(error){
throw new Error(error);
})
複製程式碼
rejected
狀態的promise
丟擲異常,相當於
promise.then(undefined, onRejected)
複製程式碼
then & catch
結合示例
promise.then(function f1(value){
//do sth 1
}).then(function f2(value){
//do sth 2
}).then(function f3(value){
//do sth 3
}).catch(function(error){
console.log(error);
})
複製程式碼
Promise.prototype.finally
promise.finally(onFinally)
複製程式碼
返回一個Promise
,在promise
執行結束時,無論結果是fulfilled
或者是rejected
,在執行then()
和catch()
後,都會執行
Promise.all
promise.all([promise1, promise2, promise3]).then(resolve);
複製程式碼
示例
// `delay`毫秒後執行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
// 所有promise變為resolve後程式退出
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
// 約128ms
console.log(values); // [1,32,64,128]
});
複製程式碼
在接收到所有的物件promise
都變為 FulFilled
返回一個resolve(array);
或者 某一個promise
物件變成Rejected
狀態返回resolve(err)
傳遞給 Promise.all
的promise
並不是一個個的順序執行的,而是同時開始、並行執行的
Promise.race
promise.race([promise1, promise2]).then(resolve, reject)
複製程式碼
示例
// `delay`毫秒後執行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
// 任何一個promise變為resolve或reject 的話程式就停止執行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});
複製程式碼
只要有一個promise
物件進入 FulFilled
或者 Rejected
狀態的話,就會繼續進行後面的處理。
Promise polyfill & Test
promise-polyfill.js
學習完Promise
後,必定要重寫Promise
,後續遇到瀏覽器環境不支援也可有的放矢
程式碼如下
/**
* @author chenchangyuan
* @date 2019-02-23
* */
function Promise(executor){
if(typeof executor !== 'function'){
throw new Error('executor is not a function');
}
var self = this;
self.state = 'pending';//pending fulfilled rejected
self.value = null;
self.reason = null;
self.callbackResolveFn = [];
self.callbackRejectFn = [];
function resolve(value){
if(self.state === 'pending'){
self.state = 'fulfilled';
self.value = value;
self.callbackResolveFn.forEach(function(fn){
fn();
});
}
}
function reject(reason){
if(self.state === 'pending'){
self.state = 'rejected';
self.reason = reason;
self.callbackRejectFn.forEach(function(fn){
fn();
});
}
}
try{
executor(resolve, reject);
}catch(err){
reject(err);
}
}
//回溯函式
function resolvePromise(promise, x, resolve, reject){
if(promise === x) return reject(new TypeError('迴圈引用'));
var flag = false;
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
try{
var then = x.then;
if(typeof then === 'function'){
then.call(x, function(val){
if(flag) return;
flag = true;
resolvePromise(promise, val, resolve, reject);
}, function(err){
if(flag) return;
flag = true;
reject(err);
});
}else{
resolve(x);
}
} catch(err){
if(flag) return;
flag = true;
reject(err);
}
}else{
resolve(x);
}
}
//返回一個新的promise(pending:push(fn),fulfilled:resolve(val),rejected:reject(reason))
Promise.prototype.then = function(onFulfilled, onRejected){
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value){
return value;
};
onRejected = typeof onRejected === 'function' ? onRejected : function(err){
throw new Error(err);
};
var self = this,
promise2;
if(self.state === 'fulfilled'){
promise2 = new Promise(function(resolve, reject){
setTimeout(function(){
try{
//將x處理成一個原始值
var x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
})
})
}
if(self.state === 'rejected'){
promise2 = new Promise(function(resolve, reject){
setTimeout(function(){
try{
//將x處理成一個原始值
var x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
})
})
}
if(self.state === 'pending'){
promise2 = new Promise(function(resolve, reject){
self.callbackResolveFn.push(function(){
setTimeout(function(){
try{
//將x處理成一個原始值
var x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
})
});
self.callbackRejectFn.push(function(){
setTimeout(function(){
try{
//將x處理成一個原始值
var x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
})
});
})
}
return promise2;
}
Promise.prototype['catch']= function (callback) {
return this.then(undefined, callback)
}
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let arr = [];
let i = 0;
function processData(index, y) {
arr[index] = y;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y)
}, reject)
}
})
}
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
});
}
Promise.resolve = function(value){
return new Promise(function(resolve,reject){
resolve(value);
});
}
Promise.reject = function(reason){
return new Promise(function(resolve,reject){
reject(reason);
});
}
Promise.defer = Promise.deferred = function () {
var d = {};
d.promise = new Promise(function (resolve, reject) {
d.resolve = resolve;
d.reject = reject;
});
return d
}
module.exports = Promise;複製程式碼
promise-aplus-tests
由於是參(抄)考(襲)前輩的polyfill
,自己編碼測試時出現了兩處錯誤,ES6 Promise
規範的2.3.1
和2.3.4
2.3.1
2.3.4
經過改正測試成功
後記
你們的支援是我最大的動力,熬夜碼字不易,如果此文對你有幫助,請不吝star
:github.com/chenchangyu…
有興趣加筆者好友的同學請掃描下方二維碼(1.本人微信,2.微信公眾號,3.技術交流微信群),願與您成為好友共同探討技術,暢聊生活!