深入淺出:promise的各種用法


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" >


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM