異步的概念
js的異步的概念的起因?yàn)閖s是單線程語言,一次只能同時(shí)做一件事。js和dom渲染公用同一個(gè)線程,因?yàn)閖s可修改dom結(jié)構(gòu),dom渲染的時(shí)候,js必須停止,js運(yùn)行時(shí),dom停止渲染。
異步是由js單線程這個(gè)原因而來的,并不是故意搞一個(gè)來為難大家,異步的存在是解決js運(yùn)行機(jī)制的問題的。
所以我們遇到等待(網(wǎng)絡(luò)請(qǐng)求,定時(shí)任務(wù))時(shí)不能卡住,需要用異步(回調(diào)callback形式)解決。
異步和同步的區(qū)別:
同步
console.log(100);
alert(200);
console.log(300);
一個(gè)hin簡(jiǎn)單的例子,一個(gè)console打印100,接著一個(gè)alert彈出,這時(shí)下一個(gè)console是不會(huì)執(zhí)行的,如果你不點(diǎn)擊alert,那么這么地方就卡住了,后面的東西都不會(huì)執(zhí)行,瀏覽器也不會(huì)渲染。只有當(dāng)你點(diǎn)擊alert彈框的確定后,才會(huì)console打印300,這就是同步。
同步簡(jiǎn)單來說,就是一個(gè)事件的開始必須等待上一個(gè)事件的結(jié)束。同步會(huì)阻塞代碼執(zhí)行。
異步
console.log(100);
setTimeout(function() {
console.log(200)
}, 1000);
console.log(300);
又一個(gè)hin簡(jiǎn)單的例子,一個(gè)console打印100,接著是一個(gè)setTimeout定時(shí)器,1秒后執(zhí)行console打印200,那么等待這1秒就卡這兒?jiǎn)???dāng)然不行了,要繼續(xù)執(zhí)行下面的console打印300才行,然后1秒后再打印定時(shí)器內(nèi)的200。
異步就是為了解決同步的問題才誕生的,如果代碼中都是像同步的例子那樣,那樣體驗(yàn)就太差了。
每一個(gè)異步函數(shù)就是一個(gè)callback回調(diào)函數(shù)。異步不會(huì)阻塞代碼執(zhí)行。
?
異步的應(yīng)用場(chǎng)景
對(duì)于前端來說,最主要的兩個(gè)場(chǎng)景就是:
- 網(wǎng)絡(luò)請(qǐng)求(如ajax圖片加載)
- 定時(shí)任務(wù)(如setTimeout)
?
console.log('start');
$.get('./data.json', function() {
console.log(data1);
});
console.log('end');
這是一個(gè)jq ajax網(wǎng)絡(luò)請(qǐng)求的例子,這個(gè)網(wǎng)絡(luò)請(qǐng)求我們調(diào)用就行了,愛什么時(shí)候返回就什么時(shí)候返回,反正不影響后面的console就行。
?
console.log('start');
let img = document.createElement('img');
img.onload = function() {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
這是一個(gè)圖片加載的例子,這個(gè)onload也是callback函數(shù),圖片先讓它加載著,不用管它,不影響后面的console輸出end就好。
?
console.log(100);
setTimeout(function() {
console.log(200)
}, 1000);
console.log(300);
// --------------------
console.log(100);
setInterval(function() {
console.log(200)
}, 1000);
console.log(300);
setTimeout和setInterval,就不多說了。
?
異步的大坑 - callbak hell
什么是callback hell? callback hell就是 回調(diào)地獄。
場(chǎng)景:我們一般在寫代碼的時(shí)候,有時(shí)候B接口需要用到A接口返回的數(shù)據(jù),C接口需要用到B接口返回的數(shù)據(jù),這樣就會(huì)寫成三四個(gè)回調(diào)函數(shù)嵌套。這樣寫代碼,其實(shí)沒有問題。但是你沒有想過,如果是8,9個(gè)接口嵌套,是不是就原地爆炸???
像這樣:
// 獲取第一份數(shù)據(jù)
$.get(url1, (data1) => {
console.log(data1);
// 獲取第二份數(shù)據(jù)
$.get(url2, (data2) => {
console.log(data2);
// 獲取第三份數(shù)據(jù)
$.get(url3, (data3) => {
console.log(data3);
// 還可能獲取更多的數(shù)據(jù)
})
})
})
于是,為了解決這個(gè)問題,promise誕生了。
Promise
上面的代碼,我們用promise實(shí)現(xiàn)是醬的:
// 先用promise封裝一下ajax請(qǐng)求
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data);
}
error(err) {
reject(err);
}
})
})
}
// 實(shí)現(xiàn)
getData(url1).then((data1) => {
console.log(data1);
return getData(url2);
}).then(data2 => {
console.log(data2);
return getData(url3);
}).then(data3 => {
console.log(data3);
}).catch(err => console.log(err));
promise解決的問題其實(shí)還是用了callback的形式,只不過把callback形式變成了一個(gè)非嵌套的形式,變成了一個(gè)管道式的串聯(lián)的形式,這就是一個(gè)進(jìn)步,這樣就比較好理解,永遠(yuǎn)是一層的關(guān)系,不像之前,一層套一層,hin麻煩,這就是promise牛逼的地方。
注意:promise解決了不是callback的問題,而是解決了callback嵌套的問題。
下面這個(gè)例子是用promise寫的簡(jiǎn)單圖片加載:
function loadImg(src) {
const p = new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
resolve(img); // resolved
};
img.onerror = () => {
const err = new Error(`圖片加載失敗${src}`);
reject(err); // rejected
};
img.src = src;
});
return p;
}
const url = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1607687820492&di=19aaed2d40d4263b5a97432811ed6379&imgtype=0&src=http%3A%2F%2Fi.17173cdn.com%2F2fhnvk%2FYWxqaGBf%2Fcms3%2FfNTkhSbocptcacd.png';
const p = loadImg(url);
p.then(img => {
console.log(img.width); // 獲取圖片的寬
}).then(img => {
console.log(img.height); // 獲取圖片的高
}).catch(err => {
console.log(err);
})