兩者都是做異步處理的, 使異步轉為同步,目的都是為了解決異步回調產生的“回調地獄”。
同步: 順序執行,始終和前文保持在一個上下文,可以快速捕獲及處理異常。由於js是單線程,當代碼量多時容易造成阻塞,耗費時間。
異步: 由瀏覽器(多線程)提供,解決阻塞,異步任務始終在同步任務全部執行完畢后才執行,像回調函數,ajax、setTimeout。提高了執行效率,節省時間,但是會占用更多資源,不利於對進程控制。
btw… 異步任務其實是可以和同步任務同時進行的,但是異步任務的回調函數(真正的異步)一定是在同步任務全部執行完畢后執行的,因為異步回調任務一定是在js線程中完成的。即異步操作在瀏覽器線程,而異步執行回到js線程。
回調地獄: 當許多功能連續調用,回調函數層層嵌套,形成一個三角形,造成可讀性差,代碼可維護性可迭代性差,可擴展性差。
一、什么是promise(承諾)
Promise是ES6中的一個內置對象,(實際是一個構造函數,通過函數可以創建一個promise對象),作用為解決異步調用問題
特點:
①、三種狀態:pending(進行中)、resolved(已完成)、rejected(已失敗)。只有異步操作的結果可以決定當前是哪一種狀態,任何其他操作都不能改變這個狀態。
②、兩種狀態的轉化:其一,從pending(進行中)到resolved(已完成)。其二,從pending(進行中)到rejected(已失敗)。只有這兩種形式的轉變。
③、缺點:在pending階段,無法取消狀態,且無法判斷pending的進度(是剛開始還是即將結束)
二、使用promise
若需要使用帶setTimeout()。其在Chrome中可以傳多個參數
setTimeout(function (a, b) { console.log(a + b); }, 1000, 20, 50); // 70
創建一個promise實例:
var mypromise = new Promise( ( resolve,reject )=>{ //構造函數new創建對象,函數必須有 // do something... If( /*異步操作成功*/ ){ resolve(value); // pending---resolve }else{ reject(error); //pending---reject } } ) // 后續操作:執行回調函數.then()方法來操作成功或失敗要操作的事情 mypromise .then( (value)=>{ // success }, (error)=>{ // failure } )
箭頭函數只是初始化promise對象,並且接受了resolve和reject兩個函數作為參數。注意if語句不是必要的,只是要表達若異步成功,就調用resolve函數,狀態轉化,並將value值傳遞下去,因為promise還有一個.then()方法,即成功(或失敗)后的一些操作,這也是resolve和reject內置函數存在的意義。反之若異步失敗亦是。
舉例: 使用構造函數創建對象
let p = new Promise(function(resolve, reject) {
// Promise() 就是一個構造函數,通過new創建了p對象實例
console.log('i am promise');
var a = 2,b = 1;
if(a>b){
resolve(a);
}else{
reject(error)
}
});
p.then(function(value) { // 執行異步回調函數
console.log('resolved.');
},function(error){
console.log('reject')
});
console.log('Hi!');
// i am promise; Hi; resolved; //可以看出promise是異步的
三、promise.prototype.then()
then()作用:是為promise實例添加狀態發生改變時的回調函數。第一個參數(必選)是resolve狀態的回調,第二個參數(可選)是reject狀態回調的函數
then()方法是promise原型上定義的方法。輸出promise.prototype可看到內置對象
then()方法支持鏈式調用,即上一個方法調用后的結果會傳給下一個方法。
等同於:
mypromise.then(function(data) {
console.log('Contents: ' + data);
}).then( function(error) {
console.error('出錯了', error);
} )
第一個回調函數完成后,會將結果作為參數,傳遞到下一個回調函數。
四、promise.prototype.catch()
建議then()不要使用第二個參數,而使用catch()
Promise.then( function(data){ // 建議
//success
} ).catch( function(err){
//error
} )
因為catch() 方法返回的還是一個promise對象,后續可以繼續使用then()進行鏈式調用。
resolve — 對應then。只有調用了resolve才會觸發then方法的回調
reject — 對應catch。只有調用了reject才會觸發catch方法的回調
五、promise.all()
作用:將多個promise實例包裝成一個新的promise實例
Var arr = [1,2,3]
Var p = promise.all(arr).then(…)
其中數組中1,2,3都是promise對象實例。若不是,就會先調用下面講到的promise.resolve()方法,將參數先轉為實例,再下一步處理。
p的狀態(成功或失敗再執行then回調) 由數組的所有實例對象決定:
1、只有所有實例對象都是resolve,p的狀態才resolve。然后返回值也是數組,傳遞給p的回調函數;
2、當有一個實例被reject了,p狀態就會是reject。此時第一個被reject的實例返回值,會傳遞給p的回調函數。
舉例:
var p= [1,2,3,4,5,6].map( function(i){
console.log(i);
} )
promise.all(p).then(...).catch(...)
通過數組map生成6個實例對象,然后作為參數傳遞promise.all(),只有當全部為resolve時才會調用then()方法,否則調用catch()方法。
六、promise.race()
同五。也是將多個promise實例包裝成一個新的promise實例。狀態亦是。
Var p = promise.race( [1,2,3] )
七、promise.resolve()
將現有的對象轉化為promise對象。看參數的不同分4種情況…
八、promise.reject()
promise.reject(reason)方法也會返回一個新的promise實例,該實例狀態為reject。
九、理解promise
其作用就是如果前面的代碼非常耗時,就會阻塞后面要執行的代碼,所以有異步操作。
舉例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testsettimeout</title>
</head>
<body>
<div class="wrap"></div>
<script>
let promise = new Promise(function(resolve, reject) {
console.log('Promise! 我是Promise對象里的函數,我最早出現是因為我要給后面一個耗時的家伙提供數據a');
var a = 20;
resolve(a);
});
promise.then(function(value) {
console.log('哈哈哈,我得到數據a了,要開始執行嘍。我是一個非常耗時的操作,依賴前面的數據,但是我想快一點執行,這有利於用戶體驗,所以別把我放在后頭啊;我被放在了一個新的線程上,不會阻塞別人的');
for (var i = 0; i < 1000000; i++) {
var li = document.createElement("li");
document.querySelector(".wrap").appendChild(li);
}
console.log("執行完畢");
});
console.log('Hi! 我是什么都不依賴的程序,但我也想快一點執行,不能委屈我啊');
</script>
</body>
</html>
// 'Promise! 我是Promise對象里的函數...
// Hi! 我是什么都不依賴的程序...
// 哈哈哈,我得到數據a了,要開始執行嘍...
// 執行完畢
在new promise里的函數是立即執行函數,執行順序:
(匿名函數—Promise!…)-----(若還有同步任務 —Hi! …)—緊接着是promise.then回調函數的執行。所以promise.then()函數才是真正的異步執行。
Async await
Async搭配await是ES7提出的,基於promise實現,也是非阻塞的異步轉同步,讓代碼更加語義化,更有可讀性。
Await函數不能單獨使用(無效),事實上async函數會返回一個promise對象,可以使用then函數添加回調。執行函數時,一旦遇到await函數就會先返回一個promise對象,等到異步操作完成,再去執行后面的語句,若await異步操作出錯,就相當於async返回的promise對象被reject了。
async函數在function前面有個async作為標識,意思就是異步函數,里面有個await搭配使用,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其實它后面可以放任何表達式,不過我們更多的是放一個返回promise 對象的表達式,它等待的是promise 對象的執行完畢,並返回結果。每到await的地方就是程序需要等待執行后面的程序,語義化很強,下面總結一下async函數的特點:
1. 語義化強
2. 里面的await只能在async函數中使用
3. await后面的語句可以是promise對象、數字、字符串等
4. async函數返回的是一個Promsie對象
5. await語句后的Promise對象變成reject狀態時,那么整個async函數會中斷,后面的程序不會繼續執行
基於上面的async的特點,我們會用到異常捕獲機制-----try…catch…
舉例:Async搭配await發送異步請求
現在寫一個函數,讓它返回promise 對象,該函數的作用是2s 之后讓數值乘以2
// 2s 之后返回雙倍的值
function doubleAfter2seconds(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2 * num)
}, 2000);
} )
}
// 再寫一個async 函數,從而可以使用await 關鍵字, await 后面放置的就是返回promise對象的一個表達式,所以它后面可以寫上 doubleAfter2seconds 函數的調用
async function testResult() {
let first = await doubleAfter2seconds(30);
let second = await doubleAfter2seconds(50);
let third = await doubleAfter2seconds(30);
console.log(first + second + third); // 220
}
testResult(); // 調用
console.log('先執行');
6秒后,控制台輸出220, 我們可以看到,寫異步代碼就像寫同步代碼一樣了,再也沒有回調地獄了。
- 代碼執行過程:調用testResult函數,然后再里面遇到了await函數,要等待,代碼就執行到這,等后面的promise對象執行完畢,拿到promise resolve的值進行返回,然后await才會繼續向下執行。
- 具體到 我們的代碼, 遇到await 之后,代碼就暫停執行了, 等待doubleAfter2seconds(30) 執行完畢,doubleAfter2seconds(30) 返回的promise 開始執行,2秒 之后,promise resolve 了, 並返回了值為60, 這時await 才拿到返回值60, 然后賦值給result, 暫停結束,代碼繼續執行,執行 console.log語句。
- Async/await相對來說更簡潔,不用寫很多的then(),不用匿名函數處理。
讓try/catch可以同時處理同步和異步處理。在promise中就無法處理,要使用.catch()方法,代碼冗余。 - 這里強調一下等待,當js引擎在等待promise resolve 的時候,它並沒有真正的暫停工作,它可以處理其它的一些事情,如果我們在testResult函數的調用后面,console.log 一下,你發現 后面console.log的代碼先執行。
