這篇文章主要介紹“JavaScript異步編程的使用”,在日常操作中,相信很多人在JavaScript異步編程的使用問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”JavaScript異步編程的使用”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)是專業(yè)的瀘水網(wǎng)站建設(shè)公司,瀘水接單;提供成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè),網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行瀘水網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!
按照維基百科上的解釋:獨(dú)立于主控制流之外發(fā)生的事件就叫做異步。比如說有一段順序執(zhí)行的代碼
void function main() { fA(); fB(); }();
fA => fB 是順序執(zhí)行的,永遠(yuǎn)都是 fA 在 fB 的前面執(zhí)行,他們就是 同步 的關(guān)系。加入這時(shí)使用 setTimeout 將 fB 延后
void function main() { setTimeout(fA, 1000); fB(); }();
這時(shí),fA 相對(duì)于 fB 就是異步的。main 函數(shù)只是聲明了要在一秒后執(zhí)行一次 fA,而并沒有立刻執(zhí)行它。這時(shí),fA 的控制流就獨(dú)立于 main 之外。
JavaScript——天生異步的語言
因?yàn)?setTimeout 的存在,至少在被 ECMA 標(biāo)準(zhǔn)化的那一刻起,JavaScript 就支持異步編程了。與其他語言的 sleep 不同,setTimeout 是異步的——它不會(huì)阻擋當(dāng)前程序繼續(xù)往下執(zhí)行。
然而異步編程真正發(fā)展壯大,Ajax 的流行功不可沒。Ajax 中的 A(Asynchronous)真正點(diǎn)到了異步的概念——這還是 IE5、IE6 的時(shí)代。
回調(diào)函數(shù)——異步編程之痛
異步任務(wù)執(zhí)行完畢之后怎樣通知開發(fā)者呢?回調(diào)函數(shù)是最樸素的,容易想到的實(shí)現(xiàn)方式。于是從異步編程誕生的那一刻起,它就和回調(diào)函數(shù)綁在了一起。
例如 setTimeout。這個(gè)函數(shù)會(huì)起一個(gè)定時(shí)器,在超過指定時(shí)間后執(zhí)行指定的函數(shù)。比如在一秒后輸出數(shù)字 1,代碼如下:
setTimeout(() => { console.log(1); }, 1000);
常規(guī)用法。如果需求有變,需要每秒輸出一個(gè)數(shù)字(當(dāng)然不是用 setInterval),JavaScript 的初學(xué)者可能會(huì)寫出這樣的代碼:
for (let i = 1; i < 10; ++i) { setTimeout(() => { // 錯(cuò)誤! console.log(i); }, 1000); }
執(zhí)行結(jié)果是等待 1 秒后,一次性輸出了所有結(jié)果。因?yàn)檫@里的循環(huán)是同時(shí)啟了 10 個(gè)定時(shí)器,每個(gè)定時(shí)器都等待 1 秒,結(jié)果當(dāng)然是所有定時(shí)器在 1 秒后同時(shí)超時(shí),觸發(fā)回調(diào)函數(shù)。
解法也簡(jiǎn)單,只需要在前一個(gè)定時(shí)器超時(shí)后再啟動(dòng)另一個(gè)定時(shí)器,代碼如下:
setTimeout(() => { console.log(1); setTimeout(() => { console.log(2); setTimeout(() => { console.log(3); setTimeout(() => { console.log(4); setTimeout(() => { console.log(5); setTimeout(() => { // ... }, 1000); }, 1000); }, 1000) }, 1000) }, 1000) }, 1000);
層層嵌套,結(jié)果就是這樣的漏斗形代碼??赡苡腥讼氲搅诵聵?biāo)準(zhǔn)中的 Promise,可以改寫如下:
function timeout(delay) { return new Promise(resolve => { setTimeout(resolve, delay); }); } timeout(1000).then(() => { console.log(1); return timeout(1000); }).then(() => { console.log(2); return timeout(1000); }).then(() => { console.log(3); return timeout(1000); }).then(() => { console.log(4); return timeout(1000); }).then(() => { console.log(5); return timeout(1000); }).then(() => { // .. });
漏斗形代碼是沒了,但代碼量本身并沒減少多少。Promise 并沒能干掉回調(diào)函數(shù)。
因?yàn)榛卣{(diào)函數(shù)的存在,循環(huán)就無法使用。不能循環(huán),那么只能考慮遞歸了,解法如下:
let i = 1; function next() { console.log(i); if (++i < 10) { setTimeout(next, 1000); } } setTimeout(next, 1000);
注意雖然寫法是遞歸,但由于 next 函數(shù)都是由瀏覽器調(diào)用的,所以實(shí)際上并沒有遞歸函數(shù)的調(diào)用棧結(jié)構(gòu)。
Generator——JavaScript 中的半?yún)f(xié)程
很多語言都引入了協(xié)程來簡(jiǎn)化異步編程,JavaScript 也有類似的概念,叫做 Generator。
MDN 上的解釋:Generator 是一種可以中途退出之后重入的函數(shù)。他們的函數(shù)上下文在每次重入后會(huì)被保持。簡(jiǎn)而言之,Generator 與普通 Function ***的區(qū)別就是:Generator 自身保留上次調(diào)用的狀態(tài)。
舉個(gè)簡(jiǎn)單的例子:
function *gen() { yield 1; yield 2; return 3; } void function main() { var iter = gen(); console.log(iter.next().value); console.log(iter.next().value); console.log(iter.next().value); }();
代碼的執(zhí)行順序是這樣:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
請(qǐng)求 gen,得到一個(gè)迭代器 iter。注意此時(shí)并未真正執(zhí)行 gen 的函數(shù)體。
調(diào)用 iter.next(),執(zhí)行 gen 的函數(shù)體。
遇到 yield 1,將 1 返回,iter.next() 的返回值即為 { done: false, value: 1 },輸出 1
調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行 gen。
遇到 yield 2,將 2 返回,iter.next() 的返回值即為 { done: false, value: 2 },輸出 2
調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行 gen。
遇到 return 3,將 3 返回,return 表示整個(gè)函數(shù)已經(jīng)執(zhí)行完畢。iter.next() 的返回值即為 { done: true, value: 3 },輸出 3
調(diào)用 Generator 函數(shù)只會(huì)返回一個(gè)迭代器,當(dāng)用戶主動(dòng)調(diào)用了 iter.next() 后,這個(gè) Generator 函數(shù)才會(huì)真正執(zhí)行。
你可以使用 for ... of 遍歷一個(gè) iterator,例如
for (var i of gen()) { console.log(i); }
輸出 1 2,*** return 3 的結(jié)果不算在內(nèi)。想用 Generator 的各項(xiàng)生成一個(gè)數(shù)組也很簡(jiǎn)單,Array.from(gen()) 或直接用 [...gen()] 即可,生成 [1, 2] 同樣不包含***的 return 3。
Generator 是異步的嗎
Generator 也叫半?yún)f(xié)程(semicoroutine),自然與異步關(guān)系匪淺。那么 Generator 是異步的嗎?
既是也不是。前面提到,異步是相對(duì)的,例如上面的例子
function *gen() { yield 1; yield 2; return 3; } void function main() { var iter = gen(); console.log(iter.next().value); console.log(iter.next().value); console.log(iter.next().value); }();
我們可以很直觀的看到,gen 的方法體與 main 的方法體在交替執(zhí)行,所以可以肯定的說,gen 相對(duì)于 main 是異步執(zhí)行的。然而此段過程中,整個(gè)控制流都沒有交回給瀏覽器,所以說 gen 和 main 相對(duì)于瀏覽器是同步執(zhí)行的。
用 Generator 簡(jiǎn)化異步代碼
回到最初的問題:
for (let i = 0; i < 10; ++i) { setTimeout(() => { console.log(i); }, 1000); // 等待上面 setTimeout 執(zhí)行完畢 }
關(guān)鍵在于如何等待前面的 setTimeout 觸發(fā)回調(diào)后再執(zhí)行下一輪循環(huán)。如果使用 Generator,我們可以考慮在 setTimeout 后 yield 出去(控制流返還給瀏覽器),然后在 setTimeout 觸發(fā)的回調(diào)函數(shù)中 next,將控制流交還回給代碼,執(zhí)行下一段循環(huán)。
let iter; function* run() { for (let i = 1; i < 10; ++i) { setTimeout(() => iter.next(), 1000); yield; // 等待上面 setTimeout 執(zhí)行完畢 console.log(i); } } iter = run(); iter.next();
代碼的執(zhí)行順序是這樣:
請(qǐng)求 run,得到一個(gè)迭代器 iter。注意此時(shí)并未真正執(zhí)行 run 的函數(shù)體。
調(diào)用 iter.next(),執(zhí)行 run 的函數(shù)體。
循環(huán)開始,i 初始化為 1。
執(zhí)行 setTimeout,啟動(dòng)一個(gè)定時(shí)器,回調(diào)函數(shù)延后 1 秒執(zhí)行。
遇到 yield(即 yield undefined),控制流返回到***的 iter.next() 之后。因?yàn)楹竺鏇]有其他代碼了,瀏覽器獲得控制權(quán),響應(yīng)用戶事件,執(zhí)行其他異步代碼等。
1 秒后,setTimeout 超時(shí),執(zhí)行回調(diào)函數(shù) () => iter.next()。
調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行,即 console.log(i),輸出 i 的值。
一次循環(huán)結(jié)束,i 自增為 2,回到第 4 步繼續(xù)執(zhí)行
……
這樣即實(shí)現(xiàn)了類似同步 sleep 的要求。
async、await——用同步語法寫異步代碼
上面的代碼畢竟需要手工定義迭代器變量,還要手工 next;更重要的是與 setTimeout 緊耦合,無法通用。
我們知道 Promise 是異步編程的未來。能不能把 Promise 和 Generator 結(jié)合使用呢?這樣考慮的結(jié)果就是 async 函數(shù)。
用 async 得到代碼如下
function timeout(delay) { return new Promise(resolve => { setTimeout(resolve, delay); }); } async function run() { for (let i = 1; i < 10; ++i) { await timeout(1000); console.log(i); } } run();
按照 Chrome 的設(shè)計(jì)文檔,async 函數(shù)內(nèi)部就是被編譯為 Generator 執(zhí)行的。run 函數(shù)本身會(huì)返回一個(gè) Promise,用于使主調(diào)函數(shù)得知 run 函數(shù)什么時(shí)候執(zhí)行完畢。所以 run() 后面也可以 .then(xxx),甚至直接 await run()。
注意有時(shí)候我們的確需要幾個(gè)異步事件并行執(zhí)行(比如調(diào)用兩個(gè)接口,等兩個(gè)接口都返回后執(zhí)行后續(xù)代碼),這時(shí)就不要過度使用 await,例如:
const a = await queryA(); // 等待 queryA 執(zhí)行完畢后 const b = await queryB(); // 執(zhí)行 queryB doSomething(a, b);
這時(shí) queryA 和 queryB 就是串行執(zhí)行的。可以略作修改:
const promiseA = queryA(); // 執(zhí)行 queryA const b = await queryB(); // 執(zhí)行 queryB 并等待其執(zhí)行結(jié)束。這時(shí)同時(shí) queryA 也在執(zhí)行。 const a = await promiseA(); // 這時(shí) queryB 已經(jīng)執(zhí)行結(jié)束。繼續(xù)等待 queryA 執(zhí)行結(jié)束 doSomething(a, b);
我個(gè)人比較喜歡如下寫法:
const [ a, b ] = await Promise.all([ queryA(), queryB() ]); doSomething(a, b);
將 await 和 Promise 結(jié)合使用,效果更佳!
結(jié)束語
如今 async 函數(shù)已經(jīng)被各大主流瀏覽器實(shí)現(xiàn)(除了 IE)。如果要兼容舊版瀏覽器,可以使用 babel 將其編譯為 Generator。如果還要兼容只支持 ES5 的瀏覽器,還可以繼續(xù)把 Generator 編譯為 ES5。編譯后的代碼量比較大,小心代碼膨脹。
如果是用 node 寫 Server,那就不用糾結(jié)了直接用就是了。koa 是用 async 是你的好幫手。
到此,關(guān)于“JavaScript異步編程的使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
網(wǎng)站標(biāo)題:JavaScript異步編程的使用
標(biāo)題來源:http://muchs.cn/article12/ihshdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、小程序開發(fā)、建站公司、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站策劃、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)