8.Promise
8.1.什麼是異步?
要理解異步,首先,從同步代碼開始說
alert(1)
alert(2)
像上面的代碼,執行順序是從上到下,先後彈出1和2,這種代碼叫做同步代碼
alert(0)
setTimeout(function () {
alert(1);
}, 2000);
setTimeout(function () {
alert(2)
}, 1000);
alert(3)
上面代碼的彈出順序是 0 3 2 1 ,像這種不按從上到下依次執行的代碼叫做異步代碼,其實還有很多類似的異步代碼,例如:ajax請求
ajax({
type:'get',
url: 'http://xxx.com/xxx',
success: function(result){}
})
console.log(111)
異步回調嵌套問題
setTimeout(function () {
alert(1)
setTimeout(function () {
alert(2)
setTimeout(function () {
alert(3)
}, 10)
}, 100)
}, 1000)
8.2.什麼是Promise?
Promise是ES6中的異步編程解決方案,在代碼中表現爲一個對象,可以通過構造函數Promise來實例化,有了Promise對象,可以將異步操作以同步的流程表達出來,避免了回調地獄(回調函數層層嵌套)
直觀的去看看Promise到底是什麼
console.dir(Promise)
這樣一看就很明白了,Promise是一個構造函數,它身上有幾個方法,例如:reject、resolve、catch、all、race等方法就是我們常用的一些方法,還有then方法在它的原型上,也是非常常用的,後面我們會詳細講解這些方法
既然是構造函數,那麼我們就可以使用new來調用一下,簡單的使用
let p = new Promise((resolve, reject) => {
setTimeout(()=>{
//代碼執行完成
console.log('代碼執行完成');
resolve()
}, 1000)
})
Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗),上面代碼中傳入的函數有兩個參數,resolve和reject,這兩個參數都是函數塊,用於回調執行,resolve是將Promise的狀態置爲fullfiled,reject是將Promise的狀態置爲rejected,只有這兩個結果可以去操作Promise的狀態,其他任何操作都不能更改這個狀態,這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。在初學階段你可以簡單的理解爲resole就是異步執行成功後被調用的函數,reject是異步執行失敗後調用的函數
注意: 上面代碼中我們只是去new Promise() 得到一個實例,但是發現異步代碼中的語句在1秒後被執行了,也就是說只要new Promise(), 那麼promise裏面的函數就會被立即執行,這是非常重要的一個細節,我們應該做到需要的時候去執行,而不是不管什麼情況都去執行,因此,我們通常把上面的代碼包到一個函數中去,需要的時候,調用一下函數就可以了
function AsyncFn() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('代碼執行完成');
resolve()
}, 1000)
});
return p;
}
函數封裝好後到底有什麼用?在什麼情況下用?resolve拿來做什麼? 帶着這些疑問,我們繼續往下講
在Promise的原型上有一個叫做then的方法,它的作用是爲 Promise 實例添加狀態改變時的回調函數,我們首先來看看then方法的位置
console.dir(Promise)
下面我們來具體使用這個then方法
function AsyncFn() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('代碼執行完成');
resolve()
}, 1000)
});
return p;
}
AsyncFn().then(function () {
alert('異步代碼執行完成後,該我執行了')
})
代碼寫到這裏,我們已經能看出Promise的作用了,它其實已經可以把原來回調函數函數寫到異步代碼裏的這種寫法改變了,它已經把回調函數函數分離出來了,在異步代碼執行完成後,通過鏈式調用的方式來執行回調函數函數,如果僅僅是向上面的代碼只執行一次回調函數可能看不出Promise帶來的好處,下面我們來看更復雜的代碼
function AsyncFn1() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('異步代碼1代碼執行完成');
resolve()
}, 1000)
});
return p;
}
function AsyncFn2() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('異步代碼2執行完成');
resolve()
}, 3000)
});
return p;
}
function AsyncFn3() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('異步代碼3執行完成');
resolve()
}, 2000)
});
return p;
}
需求:AsyncFn3 是依賴於AsyncFn2的 AsyncFn2是依賴於AsyncFn1的,這就要求AsyncFn1執行完成後再執行AsyncFn2,AsyncFn2執行完成後執行AsyncFn3,這個時候怎麼寫?
function AsyncFn1() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('異步代碼1代碼執行完成');
resolve()
}, 1000)
});
return p;
}
function AsyncFn2() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('異步代碼2執行完成');
resolve()
}, 3000)
});
return p;
}
function AsyncFn3() {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//代碼執行完成
console.log('異步代碼3執行完成');
resolve()
}, 2000)
});
return p;
}
//需求:AsyncFn3 是依賴於AsyncFn2的 AsyncFn2是依賴於AsyncFn1的,這就要求AsyncFn1執行完成後
// 再執行AsyncFn2,AsyncFn2執行完成後執行AsyncFn3,這個時候怎麼寫?
AsyncFn1().then(()=>{
alert('異步代碼1執行完成後,該我執行了');
//上面代碼執行完成後,返回一個Promise對象
return AsyncFn2()
}).then(()=>{
alert('異步代碼2執行完成後,該我執行了');
return AsyncFn3()
}).then(()=>{
alert('異步代碼3執行完成後,該我執行了');
})
到底爲止,Promise的作用已經差不多可以理解了,它是ES6中的異步解決方案,可以將異步的代碼以同步的形式表現出來,避免回調函數函數嵌套
如果理解了resolve的話,那麼理解reject就比較容易了,它是異步代碼執行失敗後執行的回調函數。reject的作用就是把Promise的狀態置爲rejected,這樣我們在then中就能捕捉到,然後執行“失敗”情況的回調
let oBtn = document.getElementById('btn');
let oResult = document.getElementById('result');
oBtn.onclick = () => {
AsyncGetData().then(() => {
oResult.innerHTML = '執行成功,獲取到了數據。。。'
}, () => {
oResult.innerHTML = '<span style="color: red">執行失敗,沒有獲取到數據。。。</span>'
})
};
function AsyncGetData() {
let num = Math.random() * 20;
return new Promise((resolve, reject) => {
setTimeout(() => {
if( num > 10){
resolve();
}else{
reject();
}
})
})
}
8.3.實例練習
1.異步加載圖片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">獲取圖片</button>
<script>
const arr = [
'http://edu.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
];
function AsyncLoadImg(url) {
let p = new Promise((resolve, reject) => {
let oImg = new Image();
oImg.src = url;
oImg.onload = () => {
resolve(oImg)
};
oImg.onerror = () => {
let error = new Error('圖片加載失敗');
reject(error)
}
});
return p;
}
let oBtn = document.getElementById('btn');
oBtn.onclick = () => {
//無序加載
// for (let i = 0; i < arr.length; i++) {
// AsyncLoadImg(arr[i]).then(function (oResult) {
// document.body.appendChild(oResult);
// })
// }
//按順序加載
AsyncLoadImg(arr[0]).then((oResult) => {
oResult.title = '圖片1';
document.body.appendChild(oResult);
return AsyncLoadImg(arr[1])
}).then((oResult) => {
oResult.title = '圖片2';
document.body.appendChild(oResult);
return AsyncLoadImg(arr[2])
}).then((oResult) => {
oResult.title = '圖片3';
document.body.appendChild(oResult);
return AsyncLoadImg(arr[3])
}).then((oResult) => {
oResult.title = '圖片4';
document.body.appendChild(oResult);
})
}
</script>
</body>
</html>
8.4.Promise相關方法
1.catch的用法
catch方法和then的第二個參數作用差不多,都是用來指定異步執行失敗後的回調函數函數的,不過,它還有一個功能就是如果在resolve中拋出錯誤,不會阻塞執行,而是可以在catch中捕獲到錯誤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">模擬獲取數據</button>
<p id="result"></p>
<script>
let oBtn = document.getElementById('btn');
let oResult = document.getElementById('result');
oBtn.onclick = () => {
AsyncGetData().then(() => {
oResult.innerHTML = '執行成功,獲取到了數據。。。'
throw new Error('這裏報錯了')
}).catch((e) => {
console.log(e)
})
};
function AsyncGetData() {
let num = Math.random() * 20;
return new Promise((resolve, reject) => {
setTimeout(() => {
if( num > 10){
resolve();
}else{
reject();
}
})
})
}
</script>
</body>
</html>
2.all方法
all方法中傳入一個數組,裏面是多個Promise實例,只有當所有的Promise實例的狀態變爲fulfilled的時候,整體的狀態纔會變成fulfilled,這個時候每個Promise的實例返回的值會組成一個數組傳給回調函數,如果整個數組中的Promise實例中有一個的狀態是rejected,那麼整體的狀態都會是rejected,這個時候,第一個rejected實例的返回值會傳給回調函數
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">獲取圖片</button>
<script>
const arr = [
'http://edu.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
];
function AsyncLoadImg(url) {
let p = new Promise((resolve, reject) => {
let oImg = new Image();
oImg.src = url;
oImg.onload = () => {
resolve(oImg)
};
oImg.onerror = () => {
let error = new Error('圖片加載失敗');
reject(error)
}
});
return p;
}
let oBtn = document.getElementById('btn');
oBtn.onclick = () => {
Promise.all([AsyncLoadImg(arr[0]),AsyncLoadImg(arr[1]),AsyncLoadImg(arr[2]),AsyncLoadImg(arr[3])])
.then((result) => {
// console.log(result)
for(let i in result){
document.body.appendChild(result[i]);
}
})
}
</script>
</body>
</html>
all方法通常適用於先加載資源,再執行操作的場景,例如:前面我們寫的貪吃蛇項目,首先去加載地圖、圖片、以及聲音等這些資源,等加載成功後再執行初始化
3.race方法
Promise.race方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">獲取圖片</button>
<script>
const arr = [
'http://edus.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
];
function AsyncLoadImg(url) {
let p = new Promise((resolve, reject) => {
let oImg = new Image();
oImg.src = url;
oImg.onload = () => {
resolve(oImg)
};
});
return p;
}
//圖片超時測試
function timeOut() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('圖片超時'));
}, 1000)
})
}
let oBtn = document.getElementById('btn');
oBtn.onclick = () => {
let p = Promise.race([AsyncLoadImg(arr[0]), timeOut()])
.then((result) => {
document.body.appendChild(result)
}).catch((err) => {
console.log(err);
})
}
</script>
</body>
</html>