https://mp.weixin.qq.com/s?__biz=MzAwNTAzMjcxNg==&mid=2651425195&idx=1&sn=eed6bea35323c75f0c43ae61818c0a55&chksm=80dff7c8b7a87edeb834cc4aabf0eec40c7566b45abd5c58b56625dc0efd77d15c9e64534140&mpshare=1&scene=1&srcid=02260hYIB6d5lSLVwyvPIUWX#rd
Promise是一個構造函數,自己身上有all、reject、resolve這幾個眼熟的方法,原型上有then、catch等同樣很眼熟的方法。
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('執行完成');
resolve('隨便什么數據');
}, 2000);
});
二、使用promise的好處是:
1.代碼結構更加扁平且更可讀,清晰明了。
2.能解決回調地獄問題。
3.可將數據請求和業務邏輯分離開來。
4.便於管理維護。
5.能更好的捕獲錯誤。
1.優點和缺點
可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易。
Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
2.Promise規范如下:
一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)
一個promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換
promise必須實現then方法(可以說,then就是promise的核心),而且then必須返回一個promise,同一個promise的then可以調用多次,並且回調的執行順序跟它們被定義時的順序一致
then方法接受兩個參數,第一個參數是成功時的回調,在promise由“等待”態轉換到“完成”態時調用,另一個是失敗時的回調,在promise由“等待”態轉換到“拒絕”態時調用。同時,then可以接受另一個promise傳入,也接受一個“類then”的對象或方法,即thenable對象。
3.promise的特性:
(1.)立即執行性
var p=new Promise(function(resolve,reject)(){
console.log("create new promise");
resolve("success");
});
console.log("after new promise");
p.then(function(value){
console.log(value);
});
//create new promise
//after new promise
//success
(2.)狀態不可逆,鏈式調用
var p = new Promise(function(resolve, reject){
resolve(1);
});
p.then(function(value){ //第一個then
console.log(value);
return value*2;
}).then(function(value){ //第二個then
console.log(value);
}).then(function(value){ //第三個then
console.log(value);
return Promise.resolve('resolve');
}).then(function(value){ //第四個then
console.log(value);
return Promise.reject('reject');
}).then(function(value){ //第五個then
console.log('resolve: '+ value);
}, function(err){
console.log('reject: ' + err);
})
//1
//2
//undefined
//resolve
//reject: reject
(3.)回調異步性
var p = new Promise(function(resolve, reject){
resolve("success");
});
p.then(function(value){
console.log(value);
});
console.log("first");
//"first"
//"success"
4.用法
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 運行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
原文參考:https://blog.csdn.net/qq_29849641/article/details/54970328
三、promise的回調地獄
一般我們要在一個函數執行完之后執行另一個函數我們稱之為callback‘回調’,簡單的寫一下:
setTimeout(
function () {
left(
function () {
setTimeout(
function () {
left(
function () {
setTimeout(
function () {
left();
},
2000);
});
},
2000);
});
},
2000);
以上代碼就是傳說中的回調地獄,如果有多層業務邏輯嵌套的話,不僅會使代碼閱讀困難,而且后面維護起來也是難點。
之后在ES6,Promise就應運而生。
四、promise的than用法
var
promise =
new
Promise(
function(
resolve,
reject) {
// ... some code
if (
/* 異步操作成功 */){
resolve(
value);
}
else {
reject(
error);
}
});
resolve(value)是在Promise在已經異步完成成功(Resolved)之后執行的
reject(value)是在Promise在異步失敗之后(Rejected)執行。
當然,也可以用then來指定:then(resolve, reject)
或者:then(resolve),catch (reject)
promise.
then(
function(
value) {
// success
},
function(
error) {
// failure
});
//等價於:
promise.
then(
function(){
//success
}).
catch(
function(){
//failure
})
總結:
1.可以采用連續的then鏈式操作來寫回調(這是因為返回值一直是新的Promise實例)。
2.以上例子可以看出來只要在第一個promise回調中添加resolve,之后的連續then就會默認執行。
可以在then中return出數據,並且這個數據會以參數的形式傳入下一個then。
var
p =
new
Promise(
function (
resolve,
reject) {
var
a =
1
resolve(
a);
}).
then(
function (
data) {
console.
log(
data)
return ++
data;
}).
then(
function (
data) {
console.
log(
data)
})
五、
reject的用法
reject的作用就是把Promise的狀態置為rejected,這樣我們在then中就能捕捉到,然后執行“失敗”情況的回調。看下面的代碼。
function
getNumber() {
var
p =
new
Promise(
function (
resolve,
reject) {
//做一些異步操作
setTimeout(
function () {
var
num =
Math.
ceil(
Math.
random() *
10);
//生成1-10的隨機數
if (
num <=
5) {
resolve(
num);
}
else {
reject(
'數字太大了');
}
},
2000);
});
return
p;
}
getNumber()
.
then(
function (
data) {
console.
log(
'resolved');
console.
log(
data);
},
function (
reason,
data) {
console.
log(
'rejected');
console.
log(
reason);
}
);
getNumber函數用來異步獲取一個數字,2秒后執行完成,如果數字小於等於5,我們認為是“成功”了,調用resolve修改Promise的狀態。否則我們認為是“失敗”了,調用reject並傳遞一個參數,作為失敗的原因。
運行getNumber並且在then中傳了兩個參數,then方法可以接受兩個參數,第一個對應resolve的回調,第二個對應reject的回調。所以我們能夠分別拿到他們傳過來的數據。多次運行這段代碼,你會隨機得到下面兩種結果:

六、catch的用法(Promise.prototype.catch())
Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。
我們知道Promise對象除了then方法,還有一個catch方法,它是做什么用的呢?其實它和then的第二個參數一樣,用來指定reject的回調,用法是這樣:
getNumber()
.
then(
function (
data) {
console.
log(
'resolved');
console.
log(
data);
})
.
catch(
function (
reason) {
console.
log(
'rejected');
console.
log(
reason);
});
效果和寫在then的第二個參數里面一樣。不過它還有另外一個作用:在執行resolve的回調(也就是上面then中的第一個參數)時,如果拋出異常了(代碼出錯了),那么並不會報錯卡死js,而是會進到這個catch方法中。請看下面的代碼:
getNumber()
.
then(
function (
data) {
console.
log(
'resolved');
console.
log(
data);
console.
log(
somedata);
//此處的somedata未定義
})
.
catch(
function (
reason) {
console.
log(
'rejected');
console.
log(
reason);
});
在resolve的回調中,我們console.log(somedata);而somedata這個變量是沒有被定義的。如果我們不用Promise,代碼運行到這里就直接在控制台報錯了,不往下運行了。但是在這里,會得到這樣的結果:

也就是說進到catch方法里面去了,而且把錯誤原因傳到了reason參數中。即便是有錯誤的代碼也不會報錯了,這與我們的try/catch語句有相同的功能。
七、all的用法
Promise的all方法提供了並行執行異步操作的能力,並且在所有異步操作執行完后才執行回調。我們仍舊使用上面定義好的runAsync1、runAsync2、runAsync3這三個函數,看下面的例子:
Promise
.
all([
runAsync1(),
runAsync2(),
runAsync3()])
.
then(
function (
results) {
console.
log(
results);
});
用Promise.all來執行,all接收一個數組參數,里面的值最終都算返回Promise對象。這樣,三個異步操作的並行執行的,等到它們都執行完后才會進到then里面。那么,三個異步操作返回的數據哪里去了呢?都在then里面呢,all會把所有異步操作的結果放進一個數組中傳給then,就是上面的results。所以上面代碼的輸出結果就是:
有了all,你就可以並行執行多個異步操作,並且在一個回調中處理所有的返回數據,是不是很酷?有一個場景是很適合用這個的,一些游戲類的素材比較多的應用,打開網頁時,預先加載需要用到的各種資源如圖片、flash以及各種靜態文件。所有的都加載完后,我們再進行頁面的初始化。
八、race的用法
all方法的效果實際上是「誰跑的慢,以誰為准執行回調」,那么相對的就有另一個方法「誰跑的快,以誰為准執行回調」,這就是race方法,這個詞本來就是賽跑的意思。race的用法與all一樣,我們把上面runAsync1的延時改為1秒來看一下:
Promise
.
race([
runAsync1(),
runAsync2(),
runAsync3()])
.
then(
function (
results) {
console.
log(
results);
});
你猜對了嗎?不完全,是吧。在then里面的回調開始執行時,runAsync2()和runAsync3()並沒有停止,仍舊再執行。於是再過1秒后,輸出了他們結束的標志。
這個race有什么用呢?使用場景還是很多的,比如我們可以用race給某個異步請求設置超時時間,並且在超時后執行相應的操作,代碼如下:
//請求某個圖片資源
function
requestImg() {
var
p =
new
Promise(
function (
resolve,
reject) {
var
img =
new
Image();
img.
onload =
function () {
resolve(
img);
}
img.
src =
'xxxxxx';
});
return
p;
}
//延時函數,用於給請求計時
function
timeout() {
var
p =
new
Promise(
function (
resolve,
reject) {
setTimeout(
function () {
reject(
'圖片請求超時');
},
5000);
});
return
p;
}
Promise
.
race([
requestImg(),
timeout()])
.
then(
function (
results) {
console.
log(
results);
})
.
catch(
function (
reason) {
console.
log(
reason);
});
requestImg函數會異步請求一張圖片,我把地址寫為"xxxxxx",所以肯定是無法成功請求到的。timeout函數是一個延時5秒的異步操作。我們把這兩個返回Promise對象的函數放進race,於是他倆就會賽跑,如果5秒之內圖片請求成功了,那么遍進入then方法,執行正常的流程。如果5秒鍾圖片還未成功返回,那么timeout就跑贏了,則進入catch,報出“圖片請求超時”的信息。運行結果如下:
九、Promise.prototype.finally()
finally方法用於指定不管 Promise 對象最后狀態如何,都會執行的操作。該方法是
ES2018 引入標准的
promise
.
then(
result
=> { ··· })
.
catch(
error
=> { ··· })
.
finally(()
=> { ··· });
上面代碼中,不管promise最后的狀態,在執行完then或catch指定的回調函數以后,都會執行finally方法指定的回調函數。
下面是一個例子,服務器使用 Promise 處理請求,然后使用finally方法關掉服務器。
pasting
server.
listen(
port)
.
then(
function () {
// ...
})
.
finally(
server.
stop);
finally方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態到底是fulfilled還是rejected。這表明,finally方法里面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。
finally本質上是then方法的特例。
promise
.
finally(()
=> {
// 語句
});
// 等同於
promise
.
then(
result
=> {
// 語句
return
result;
},
error
=> {
// 語句
throw
error;
}
);
上面代碼中,如果不使用finally方法,同樣的語句需要為成功和失敗兩種情況各寫一次。有了finally方法,則只需要寫一次。
它的實現也很簡單。
Promise.
prototype.
finally =
function (
callback) {
let
P =
this.
constructor;
return
this.
then(
value
=>
P.
resolve(
callback()).
then(()
=>
value),
reason
=>
P.
resolve(
callback()).
then(()
=> {
throw
reason })
);
};
上面代碼中,不管前面的 Promise 是fulfilled還是rejected,都會執行回調函數callback。
從上面的實現還可以看到,finally方法總是會返回原來的值。
// resolve 的值是 undefined
Promise.
resolve(
2).
then(()
=> { }, ()
=> { })
// resolve 的值是 2
Promise.
resolve(
2).
finally(()
=> { })
// reject 的值是 undefined
Promise.
reject(
3).
then(()
=> { }, ()
=> { })
// reject 的值是 3
Promise.
reject(
3).
finally(()
=> { })
十、Promise.try()
實際開發中,經常遇到一種情況:不知道或者不想區分,函數f是同步函數還是異步操作,但是想用 Promise 來處理它。因為這樣就可以不管f是否包含異步操作,都用then方法指定下一步流程,用catch方法處理f拋出的錯誤。一般就會采用下面的寫法。
Promise.
resolve().
then(
f)
上面的寫法有一個缺點,就是如果f是同步函數,那么它會在本輪事件循環的末尾執行。
const
f = ()
=>
console.
log(
'now');
Promise.
resolve().
then(
f);
console.
log(
'next');
// next
// now
上面代碼中,函數f是同步的,但是用 Promise 包裝了以后,就變成異步執行了。
那么有沒有一種方法,讓同步函數同步執行,異步函數異步執行,並且讓它們具有統一的 API 呢?回答是可以的,並且還有兩種寫法。第一種寫法是用async函數來寫。
const
f = ()
=>
console.
log(
'now');
(
async ()
=>
f())();
console.
log(
'next');
// now
// next
上面代碼中,第二行是一個立即執行的匿名函數,會立即執行里面的async函數,因此如果f是同步的,就會得到同步的結果;如果f是異步的,就可以用then指定下一步,就像下面的寫法。
(
async ()
=>
f())()
.
then(...)
需要注意的是,async () => f()會吃掉f()拋出的錯誤。所以,如果想捕獲錯誤,要使用promise.catch方法。
pasting
(
async ()
=>
f())()
.
then(...)
.
catch(...)
第二種寫法是使用new Promise()。
const
f = ()
=>
console.
log(
'now');
(
()
=>
new
Promise(
resolve
=>
resolve(
f())
)
)();
console.
log(
'next');
// now
// next
上面代碼也是使用立即執行的匿名函數,執行new Promise()。這種情況下,同步函數也是同步執行的。
鑒於這是一個很常見的需求,所以現在有一個提案,提供Promise.try方法替代上面的寫法。
const
f = ()
=>
console.
log(
'now');
Promise.
try(
f);
console.
log(
'next');
// now
// next
事實上,Promise.try存在已久,Promise 庫Bluebird、Q和when,早就提供了這個方法。
由於Promise.try為所有操作提供了統一的處理機制,所以如果想用then方法管理流程,最好都用Promise.try包裝一下。這樣有許多好處,其中一點就是可以更好地管理異常。
function
getUsername(
userId) {
return
database.
users.
get({
id:
userId})
.
then(
function(
user) {
return
user.
name;
});
}
上面代碼中,database.users.get()返回一個 Promise 對象,如果拋出異步錯誤,可以用catch方法捕獲,就像下面這樣寫。
database.
users.
get({
id:
userId })
.
then(...)
.
catch(...)
但是database.users.get()可能還會拋出同步錯誤(比如數據庫連接錯誤,具體要看實現方法),這時你就不得不用try...catch去捕獲。
pasting
try {
database.
users.
get({
id:
userId })
.
then(...)
.
catch(...)
}
catch (
e) {
// ...
}
上面這樣的寫法就很笨拙了,這時就可以統一用promise.catch()捕獲所有同步和異步的錯誤。
Promise.
try(
database.
users.
get({
id:
userId }))
.
then(...)
.
catch(...)
事實上,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊。
原網站:https://www.cnblogs.com/whybxy/p/7645578.html
https://www.cnblogs.com/sunshq/p/7890504.html
推薦阮一峰promise:http://es6.ruanyifeng.com/#docs/promise" target="_blank" >