JavaScript異步編程操作實(shí)現(xiàn)介紹
異步編程
目前主流的JavaScript執(zhí)行環(huán)境都是以單線程執(zhí)行JavaScript的。
JavaScript早期只是一門負(fù)責(zé)在瀏覽器端執(zhí)行的腳本語言,主要用來操作DOM,如果其添加的同時(shí)又刪除了DOM,瀏覽器就不知道該如何是好,所以其就被設(shè)計(jì)成為單線程模型。而隨著JavaScript能做的事情越來越多,如果一直維持同步編程的話,就會(huì)導(dǎo)致瀏覽器卡在某個(gè)耗時(shí)操作無法進(jìn)行下一步,造成瀏覽器假死的現(xiàn)象,影響用戶體驗(yàn)。因此,異步編程應(yīng)運(yùn)而生。
同步模式與異步模式
同步模式(Synchronous)
同步模式是指代碼是同步執(zhí)行的,下一步的代碼執(zhí)行必須要等到上一步的代碼完成之后才能執(zhí)行,執(zhí)行順序就為代碼的編寫順序。
console.log('global begin')
function bar() {
console.log('bar task')
}
function foo() {
console.log('foo task')
bar()
}
foo()
console.log('global end')異步模式(Asynchronous)
不會(huì)去等待這個(gè)任務(wù)的執(zhí)行完成才去執(zhí)行下一個(gè)任務(wù),開啟過后就立即開始下一個(gè)任務(wù),后續(xù)邏輯一般會(huì)通過回調(diào)函數(shù)來進(jìn)行定義。
console.log('global begin')
setTimeout(function timer1 () {
console.log('timer1 invoke')
}, 1800)
setTimeout(function timer2 () {
console.log('timer2 invoke')
setTimeout(function inner () {
console.log('inner invoke')
}, 1000)
}, 1000)
console.log('global end')回調(diào)函數(shù)
回調(diào)函數(shù)——所有異步編程方案的根基
其實(shí)回調(diào)函數(shù)就是封裝你想要對(duì)某些數(shù)據(jù)進(jìn)行的操作,等到你想要進(jìn)行的操作結(jié)束后,再調(diào)用這個(gè)函數(shù)。
一講起回調(diào)函數(shù),面試中一般都會(huì)被問到,什么是回調(diào)地獄?如何解決回調(diào)地獄。以下面代碼為例:
// 回調(diào)地獄,只是示例,不能運(yùn)行
$.get('/url1', function (data1) {
$.get('/url2', data1, function (data2) {
$.get('/url3', data2, function (data3) {
$.get('/url4', data3, function (data4) {
$.get('/url5', data4, function (data5) {
$.get('/url6', data5, function (data6) {
$.get('/url7', data6, function (data7) {
// 略微夸張了一點(diǎn)點(diǎn)
})
})
})
})
})
})
})一大串的回調(diào)不僅難以閱讀,當(dāng)代碼出現(xiàn)錯(cuò)誤時(shí),找出代碼錯(cuò)誤更是一種折磨。幸運(yùn)的是,JavaScript是在不斷發(fā)展的,在ES2015(ES6)中,出現(xiàn)了一種解決方法,媽媽再也不用擔(dān)心我寫代碼碰到回調(diào)地獄了。
Promise
Promise——一種更優(yōu)的異步編程統(tǒng)一方案
你可以把Promise理解成“承諾”或者“期約”(js高程作者的翻譯,想了解的話,可以去看看紅寶書第四版),你已經(jīng)聲明了這個(gè)東西,它在未來的時(shí)間一定會(huì)執(zhí)行,你可以相信它。
首先,你要了解Promise是有三種狀態(tài),即pending(等待)、onFulfilled(完成)、onRejected(失敗),完成或失敗狀態(tài)一旦確定,就是無法更改的。
Promise基本用法
const promise = new Promise(function (resolve, reject) {
// 注意,要得到reject的結(jié)果時(shí)要先把resolved的代碼注釋掉,原因上面已經(jīng)解釋了
resolve(100) // 兌現(xiàn)承諾
reject(new Error('promise rejected')) // 承諾失敗
})
promise.then(function(value) {
console.log('resolved', value)
}, function(error) {
console.log('rejected', error)
})
console.log('end')每個(gè)new Promise都接受兩個(gè)參數(shù),第一個(gè)為兌現(xiàn)承諾的函數(shù),會(huì)將函數(shù)中的值傳遞給promise實(shí)例,reject等同。而返回的promise又自帶一個(gè)then方法,也接受兩個(gè)參數(shù),一個(gè)代表成功的回調(diào),一個(gè)代表失敗的回調(diào)。
Promise使用案例
在當(dāng)前文件夾下新建一個(gè)文件夾,其中隨便放兩個(gè)json文件,用來模擬ajax請(qǐng)求。
// Promise 方式的 AJAX
function ajax(url) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function() {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('./api/posts.json').then(function (res) {
console.log(res)
}, function(err) {
console.log(err)
})注意,上述代碼應(yīng)該在瀏覽器端運(yùn)行。
Promise常見誤區(qū)
有人學(xué)了promise之后,可能還是會(huì)寫出這樣的代碼:
ajax('/api/urls.json').then(function (urls) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
})
})
})
})
})說實(shí)話,這樣寫還不如不寫。正經(jīng)寫法是鏈?zhǔn)秸{(diào)用,學(xué)過jQuery的同學(xué)應(yīng)該不會(huì)陌生吧。
ajax('/api/users.json')
.then(function (value) {
console.log(1111)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(2222)
console.log(value)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(3333)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(4444)
return 'foo'
}) // => Promise
.then(function (value) {
console.log(5555)
console.log(value)
})Promise異常處理
通過.catch方法來捕獲異常。
ajax('/api/users.json')
.then(function onFulfilled (value) {
console.log('onFulfilled', value)
return ajax('/error-url')
})
.catch(function onRejected (error) {
console.log('onRejected', error)
})其實(shí)catch方法和then方法實(shí)現(xiàn)差不太多,不過是then方法第一個(gè)參數(shù)傳入undefine,一個(gè)語法糖而已。還有一個(gè)有意思的現(xiàn)象是,當(dāng)中間的then出現(xiàn)錯(cuò)誤時(shí),會(huì)直接穿透到最后的catch方法,有興趣了解怎么實(shí)現(xiàn)的可以去看看源碼。相信你一定會(huì)有所收獲。
Promise靜態(tài)方法
- Promise.resolve
Promise.resolve('foo')
.then(function (value) {
console.log(value)
})
該方法會(huì)直接將傳入的內(nèi)容當(dāng)作一個(gè)onFulfilled對(duì)象返回;其也可以傳入一個(gè)Promise對(duì)象,直接返回Promise.resolve方法;傳入一個(gè)帶有then方法的函數(shù)也同理。
- Promise.reject
該方法和上述一樣調(diào)用,不過后面是接一個(gè)catch。
- Promise.all
該方法接受一個(gè)數(shù)組,會(huì)等待數(shù)組內(nèi)的方法全部調(diào)用完后再返回一個(gè)數(shù)組對(duì)象。
- Promise.race
該方法會(huì)返回最先完成的promise。
宏任務(wù)與微任務(wù)
// 微任務(wù)
console.log('global start')
// setTimeout 的回調(diào)是 宏任務(wù),進(jìn)入回調(diào)隊(duì)列排隊(duì)
setTimeout(() => {
console.log('setTimeout')
}, 0)
// Promise 的回調(diào)是 微任務(wù),本輪調(diào)用末尾直接執(zhí)行
Promise.resolve()
.then(() => {
console.log('promise')
})
.then(() => {
console.log('promise 2')
})
.then(() => {
console.log('promise 3')
})
console.log('global end')每次調(diào)用宏任務(wù)之前,都得確保微任務(wù)隊(duì)列清空,所以也就能理解上面為什么會(huì)按照那樣的順序進(jìn)行輸出。
常見的宏任務(wù)有
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
常見的微任務(wù)有
- promise
- nextTick
- mutationObserver
Generator 異步方案
ES6也推出了Generator異步解決方案。首先來看下生成器函數(shù)如何使用。
生成器函數(shù)回顧
function *foo() {
console.log('satrt')
try {
const res = yield 'foo'
console.log(res)
} catch (e) {
console.log(e)
}
}
const generator = foo()
const result = generator.next()
console.log(result)
generator.throw(new Error('Generato error'))生成器函數(shù)比普通函數(shù)多了個(gè) * ,其放左放右都無所謂,我個(gè)人傾向于放右邊。
其內(nèi)部有一個(gè)next方法,返回一個(gè)對(duì)象,大概就是這個(gè)樣子
{ value: 'foo', done: false }
value為yield返回的值,當(dāng)然,如果你調(diào)用yield時(shí)傳入了值,返回的值就是你傳入的值。當(dāng)執(zhí)行完畢時(shí),done就變成了true。
function * main () {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
function co (generator) {
const g = generator()
function handleResult (result) {
if (result.done) return // 生成器函數(shù)結(jié)束
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)你只要理解了這個(gè),即要通過你調(diào)用next方法才會(huì)進(jìn)行到下一步,否則代碼就會(huì)停在yield那里。不過,你這樣每次享受優(yōu)美代碼時(shí)都還是需要自己編寫一個(gè)co函數(shù),未免有點(diǎn)太過麻煩。不過不用擔(dān)心,async就要出場(chǎng)了。
async與await
async、await——可能是異步的終極解決方案
async function main () {
try {
const users = await ajax('/api/users.json')
console.log(users)
const posts = await ajax('/api/posts.json')
console.log(posts)
const urls = await ajax('/api/urls.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}你只需要在函數(shù)定義前加一個(gè)async,并在你想要等待的完成操作后的函數(shù)前加一個(gè)await,就可以實(shí)現(xiàn)同步的書寫代碼而異步調(diào)用,怎么樣?這是不是更加優(yōu)雅方便了呢?鑒于目前ECMAScript的發(fā)展趨勢(shì),保不準(zhǔn)哪一天不需要async,直接用await就能實(shí)現(xiàn)異步編程了。
到此這篇關(guān)于JavaScript異步編程操作實(shí)現(xiàn)介紹的文章就介紹到這了,更多相關(guān)JS異步編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mpvue網(wǎng)易云短信接口實(shí)現(xiàn)小程序短信登錄的示例代碼
這篇文章主要介紹了mpvue網(wǎng)易云短信接口實(shí)現(xiàn)小程序短信登錄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
JS組件Bootstrap實(shí)現(xiàn)圖片輪播效果
這篇文章主要為大家詳細(xì)介紹了JS組件Bootstrap實(shí)現(xiàn)圖片輪播效果的具體代碼,對(duì)圖片輪播效果感興趣的小伙伴們可以參考一下2016-05-05
Js實(shí)現(xiàn)京東無延遲菜單效果實(shí)例(demo)
本篇文章主要介紹了Js實(shí)現(xiàn)京東無延遲菜單效果實(shí)例(demo) ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
基于jquery的高性能td和input切換并可修改內(nèi)容實(shí)現(xiàn)代碼
在實(shí)際工作中,我們會(huì)碰到這樣一個(gè)情況。在頁面中顯示著100個(gè)數(shù)據(jù),同時(shí)用戶還希望他可以更改其中的數(shù)據(jù),普通的方式可能如下2011-01-01
另一個(gè)javascript小測(cè)驗(yàn)(代碼集合)
共25道題,測(cè)試內(nèi)容包括運(yùn)算符,表達(dá)式,語句,javascript語言特性,JSON語法,數(shù)組,正則等。2011-07-07
主頁面中的兩個(gè)iframe實(shí)現(xiàn)鼠標(biāo)拖動(dòng)改變其大小
iframe實(shí)現(xiàn)鼠標(biāo)拖動(dòng)改變其大小在一個(gè)頁面中的兩個(gè)iframe的情況下,此方法相當(dāng)實(shí)用,感興趣的各位不妨參考下,或許對(duì)你有所幫助2013-04-04
uniapp中全局頁面掛載組件實(shí)戰(zhàn)過程(小程序)
這篇文章主要給大家介紹了關(guān)于uniapp中全局頁面掛載組件(小程序)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用uniapp具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-12-12

