c++ 异步下获取线程执行结果_前端异步编程的那些事
一、異步編程的運(yùn)行機(jī)制
????我們學(xué)習(xí)Javascript語言的時(shí)候就知道它的執(zhí)行環(huán)境是”單線程“的。
????所謂”單線程“,就是指一次只能處理一個(gè)任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面一個(gè)任務(wù)。常見的瀏覽器無響應(yīng)(假死),往往就是因?yàn)槟骋欢蜫avascript代碼長(zhǎng)時(shí)間運(yùn)行(比如死循環(huán)),導(dǎo)致整個(gè)頁(yè)面卡在這個(gè)地方,其他任務(wù)無法執(zhí)行。??
????所以為了解決“單線程”引發(fā)的出來的問題,就有了前端異步編程的這個(gè)解決方案,下面我們就來探討一下瀏覽器的異步編程是一個(gè)怎么的運(yùn)行機(jī)制。
1.1異步任務(wù)
????前端中常見的異步任務(wù)有以下:
?? ? 回調(diào)函數(shù)
????? 事件綁定
????? 定時(shí)器(settimeout ,setinterval)
????? ajax
????? Promise
????是異步的嗎?我們來驗(yàn)證下:
new Promise((resolve,reject)=>{ console.log(1); resolve();}).then(()=>{ console.log(2);})console.log(3);????我們看下控制臺(tái)的輸出順序:
????由上可見 new Promise 的時(shí)候傳遞的executor函數(shù)是立即執(zhí)行的(同步),基于then、catch存放的方法是異步執(zhí)行的。
????? async/await
????這是基于Generator語法糖,同樣也是異步的,我們把上面的例子改成這樣看看。
async function async1() {console.log(1);await 10;console.log(2);}fn();console.log(3);我們看下控制臺(tái)的輸出順序:
由上可見 await 表達(dá)式后面的代碼是異步執(zhí)行的 。
1.2Event Loop
????那么JS是如何構(gòu)建出異步編程的效果呢?那我們就來了解下事件循環(huán)機(jī)制(Event Loop )與事件隊(duì)列(Event Queue)。
1、開始,任務(wù)執(zhí)行。
2、同步任務(wù)直接在主棧(Call Stack)中等待被執(zhí)行,異步的進(jìn)入Event Table并注冊(cè)函數(shù)。當(dāng)指定的事情完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移入Event Queue。
3、當(dāng) Call Stack中沒有任務(wù)了,就從Event Queue中拿出一個(gè)任務(wù)放入Call Stack執(zhí)行。
而 Event Loop 指的就是這一整個(gè)圈圈:它不停檢查 Call Stack 中是否有任務(wù)(也叫棧幀)需要執(zhí)行,如果沒有,就檢查 Event Queue,從中彈出一個(gè)任務(wù),放入主棧中,如此往復(fù)循環(huán)。
我們來看個(gè)例子:?
setTimeout(()=>{ console.log(1);},50)setTimeout(()=>{ console.log(2);},0)console.time('for takes time:');for(let i=0;i<1000000;i++){}console.timeEnd('for takes time:');setTimeout(()=>{ console.log(3);},20)console.log(4);?我們先看下執(zhí)行結(jié)果再來分析。
代碼分析如下:
????這里主要容易疑惑點(diǎn)的點(diǎn)是T1先執(zhí)行還是T3先執(zhí)行,這里要看for 循環(huán)主要消耗的時(shí)間,for 循環(huán)用了5ms(假設(shè)是個(gè)整數(shù)),這時(shí)候隊(duì)列中T1需要等待的時(shí)間還有45ms,所以執(zhí)行完T2后,等待到了20ms 就會(huì)執(zhí)行T3,最后才執(zhí)行T1。如果把for循環(huán)耗的時(shí)間是100ms,那么執(zhí)行順序就是T2=>T1=>T3,遵守時(shí)間到先執(zhí)行,先進(jìn)隊(duì)列先執(zhí)行的原則。
????我們來看個(gè)稍微復(fù)雜點(diǎn)的例子:
async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end');}async function async2(){ console.log('async2');}console.log('script start');setTimeout(function(){ console.log('setTimeout');})async1();new Promise(function(resolve){ console.log('promise1'); resolve();}).then(function(){ console.log('promise2');})console.log('script?end');????學(xué)習(xí)上面的Event Loop ,同步任務(wù)先執(zhí)行,再遵守先進(jìn)先出的原則從Event Queue中取出異步任務(wù)到主棧中執(zhí)行,我自信的以為輸出順序?yàn)?#xff1a;
'script start' 'async1 start' 'async2' 'promise1'????'script?end'??'setTimeout'??'async1?end'???'promise2'???然后迫不及待的去控制臺(tái)執(zhí)行下:
為什么順序執(zhí)行是這樣子的?于是繼續(xù)查資料。
1.3宏任務(wù)與微任務(wù)
Javacript中事件執(zhí)行主要分為兩種任務(wù)類型:宏任務(wù)(macro task)與微任務(wù)隊(duì)列(micro task)。
????? 宏任務(wù)包含有 :
script(整體代碼)setTimeoutsetIntervalI/OUI交互事件postMessageMessageChannelsetImmediate(Node.js?環(huán)境)????? 微任務(wù)包含有 :
Promise.then/catch/finaly, async/await(本質(zhì)是對(duì)Promise 的一些封裝)Object.observeMutaionObserverprocess.nextTick(Node.js?環(huán)境)1.4宏任務(wù)與微任務(wù)運(yùn)行機(jī)制
1、執(zhí)行一個(gè)宏任務(wù)(棧中沒有就從Event Queue中取出)
2、遇到異步宏任務(wù)時(shí)它加入到Event Queue宏任務(wù)隊(duì)列中;遇到異步微任務(wù)時(shí)把它加入Event Queue微任務(wù)隊(duì)列中。
3、宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
4、當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
5、渲染完畢后,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲取)
如下圖:
所以上面例子(代碼5)的執(zhí)行分析如下圖所示:
1.5總結(jié)以上的內(nèi)容
????? javascript是一門單線程語言
????? javascript事件循環(huán)就是一個(gè)Event Loop 過程
????? Event Queue 中的又分為宏任務(wù)隊(duì)列與微任務(wù)隊(duì)列
????? Javscript代碼的執(zhí)行順序:同步任務(wù)=>異步微任務(wù)=>異步宏任務(wù)
二、異步編程使用場(chǎng)景
????上面內(nèi)容我們探討了瀏覽器異步編程是的運(yùn)行機(jī)制。我們一般會(huì)在哪些場(chǎng)景使用異步編程呢?下面我們看個(gè)例子:
????現(xiàn)在有個(gè)業(yè)務(wù)需求就是要先請(qǐng)求接口1拿到“系統(tǒng)”的下拉數(shù)據(jù)后,并且要給表單賦值為下拉數(shù)據(jù)的第一個(gè),拿到“系統(tǒng)”的value值后再請(qǐng)求接口2取到“表單分類”的下拉數(shù)據(jù),并且要給表單賦值,拿到“表單分類“的值后再請(qǐng)求接口3,取表單的下拉數(shù)據(jù)。
我們傳統(tǒng)的做法一般會(huì)用回調(diào)函數(shù):
2.1回調(diào)地獄
????回調(diào)函數(shù)是異步編程的一個(gè)傳統(tǒng)解決方案,但是如果業(yè)務(wù)比較復(fù)雜,需要層層嵌套,就會(huì)引起回調(diào)地獄。
這樣子的代碼嵌套復(fù)雜,不易于維護(hù) ,著實(shí)讓開發(fā)者崩潰 。
三、Promise原理及實(shí)現(xiàn)
3.1Promise原理及實(shí)現(xiàn)
??? Promise是異步編程的一個(gè)解決方案,相比回調(diào)函數(shù),使用Promise更為合理和強(qiáng)大,避免了回調(diào)函數(shù)之間的多嵌套,也使得代碼結(jié)構(gòu)更為清晰,便于維護(hù)。上面的例子我們用Promise來實(shí)現(xiàn)下。
new Promise((resolve,reject)=>{ //獲取系統(tǒng) _self.ajaxFn({ url:url1, success:(res)=>{ resolve(res); } })}).then( value=>{ return new Promise((resolve,reject)=>{ //獲取表單分類 _self.ajaxFn({ url:url2, success:(res)=>{ resolve(res); } }) }) }).then( value=>{ //獲取表單 _self.ajaxFn({ url:url1, success:(res)=>{ //處理邏輯 } }) })接下來我們就要分析Promise是原理并且要自己實(shí)現(xiàn)Promise。
3.2 promise自定義實(shí)現(xiàn)的關(guān)鍵點(diǎn)
1、如何改變promise的狀態(tài)?
1.1 resolve(value);狀態(tài)從pending 轉(zhuǎn)為了 resolved
1.2 rejecte(reason);狀態(tài)從pending 轉(zhuǎn)為了 rejected
1.3 拋出異常(throw);當(dāng)前的pending 就變?yōu)榱?rejected
2、Promise then/catch 存放的回調(diào)函數(shù)是異步微任務(wù)的 。
3、Promise.then()返回的新的Promise的結(jié)果狀態(tài)由什么決定的呢?
3.1 簡(jiǎn)單描述:由then指定的回調(diào)函數(shù)執(zhí)行的結(jié)果決定
3.2 詳細(xì)描述:
3.2.1 如果拋出異常,新Promise變?yōu)閞ejected, reason 為拋出的異常;
3.2.2 如果返回的是Promise的任意值,新promise變?yōu)閞esolved,value 為返回的值
3.2.3 如果返回的是另一個(gè)Promise,此Promise的結(jié)果成為新Promise的結(jié)果
4、Promise.then()/catch()可鏈?zhǔn)秸{(diào)用,所以then(),catch方法必須返回一個(gè)的Promise對(duì)象。
3.3?自定義Promise
1、定義整體結(jié)構(gòu)
(function(){ /* 構(gòu)造函數(shù) excutor:執(zhí)行器(同步執(zhí)行) */ function Promise(excutor){} /* Promise 原型對(duì)象的then() 指定成功和失敗的回調(diào)函數(shù) 因?yàn)榭涉準(zhǔn)秸{(diào)用,所以要返回一個(gè)promise對(duì)象 */ Promise.prototype.then=function(onResolved,onRejected){ } /* Promise 原型對(duì)象的catch() 指定失敗的回調(diào)函數(shù) 返回一個(gè)promise對(duì)象 */ Promise.prototype.catch=function(onRejected){ } /* 只實(shí)現(xiàn)Promise關(guān)鍵 的構(gòu)造方法以及catch/then方法,其它的方法大家可自行發(fā)揮啦 */ window.Promise=Promise;})(window)2、Promise中回調(diào)的函數(shù)的異步執(zhí)行
????第一部分內(nèi)容我們就知道Promise.then/catch中存放是任務(wù)是方式是異步執(zhí)行,且是微任務(wù)。異步執(zhí)行我們很容易就能想到setTimeout能實(shí)現(xiàn),但是setTimeout是宏任務(wù),所以不符合,于是我就找到了Vue中的nextTick的方法來模擬一下,它主要是用HTML5新的API MutationObserver(不熟悉的可自行了解下)來實(shí)現(xiàn)的,第一部分內(nèi)容我們有說到MutationObserver是微任務(wù)。
const nextTick = (function () { var callbacks = []; var pending = false; var timerFunc; function nextTickHandler () { pending = false; var copies = callbacks.slice(0); callbacks = []; for (var i = 0; i < copies.length; i++) { copies[i]() } } //istanbul ignore if ??if?(typeof?MutationObserver?!=?'undefined')?{?//?首選?MutationObserver? var counter = 1 var observer = new MutationObserver(nextTickHandler) // 聲明 MO 和回調(diào)函數(shù) var textNode = document.createTextNode(counter) observer.observe(textNode, { // 監(jiān)聽 textNode 這個(gè)文本節(jié)點(diǎn) characterData: true // 一旦文本改變則觸發(fā)回調(diào)函數(shù) nextTickHandler }) timerFunc = function () { counter = (counter + 1) % 2 // 每次執(zhí)行 timeFunc 都會(huì)讓文本在 1 和 0 間切換 textNode.data = counter } } else { timerFunc = setTimeout // 如果不支持 MutationObserver, 退選 setTimeout } return function (cb, ctx) { var func = ctx ? function () { cb.call(ctx) } : cb callbacks.push(func) if (pending) return pending = true timerFunc(nextTickHandler, 0) }})()3、Promise 的構(gòu)造函數(shù)的實(shí)現(xiàn)
/* 構(gòu)造函數(shù) excutor:執(zhí)行器*/function Promise(excutor){ const _self=this; _self.status='pending';//promise對(duì)象指定的status屬性,初始值為pending; _self.data=undefined;//promise對(duì)象指定一個(gè)用于存儲(chǔ)結(jié)果數(shù)據(jù)的屬性; _self.callbacks=[];//失敗或成功的回調(diào)函數(shù)數(shù)組,每個(gè)元素的結(jié)構(gòu):{onResolved(){},onRejected(){}} function resolve(value){ if(_self.status!='pending'){ return; } _self.status='resolved'; //將狀態(tài)改為resolved _self.data=value; // 保存value //如果有待將執(zhí)行的回調(diào)函數(shù),就立即異步執(zhí)行回調(diào)函數(shù) onResolved nextTick(()=>{ _self.callbacks.forEach((callbacksObj)=>{ callbacksObj.onResolved(value); }) }) } function reject(reason){ if(_self.status!='pending'){ return; } _self.status='rejected'; //將狀態(tài)改為rejected _self.data=reason; // 保存value //如果有待將執(zhí)行的回調(diào)函數(shù),就立即異步執(zhí)行回調(diào)函數(shù) onRejected nextTick(()=>{ _self.callbacks.forEach((callbacksObj)=>{ callbacksObj.onRejected(reason); }) }) } //立即執(zhí)行執(zhí)行器 try{ excutor(resolve,reject); } catch(error){ reject(error); } }4、Promise.prototype.then()方法的實(shí)現(xiàn)
/* Promise 原型對(duì)象的then()方法 指定成功和失敗的回調(diào)函數(shù) 因?yàn)榭涉準(zhǔn)秸{(diào)用,所以必須要要返回一個(gè)promise對(duì)象 返回的promise 的結(jié)果是由onResolved/onRejected執(zhí)行的結(jié)果決定的*/Promise.prototype.then=function(onResolved,onRejected){ var _self=this; //指定回調(diào)函數(shù)的默認(rèn)值,如果參數(shù)不是函數(shù)就指定個(gè)默認(rèn)方法 onResolved=typeof onResolved==='function'? onResolved :value => value; onRejected=typeof onRejected==='function'? onRejected :reason => {throw reason}; return new Promise((resolve,reject)=>{ /* 執(zhí)行指定callback(onResolved/onRejected)的回調(diào)函數(shù) 根據(jù)執(zhí)行的結(jié)果改變r(jià)eturn的pormise的狀態(tài) */ function handle(callback){ try{ const result=callback(_self.data); if(result instanceof Promise){ //返回的是promise,返回promise的結(jié)果就是這個(gè)結(jié)果 result.then(resolve,reject) }else{//返回的不是promise,返回的promise為成功,value就是返回值 resolve(result) } }catch(error){ //拋出異常,返回promise的結(jié)果為失敗,reason為異常 reject(error) } } if(_self.status==='resolved'){ //當(dāng)前promise的狀態(tài)是resolved //異步執(zhí)行成功的回調(diào) nextTick(()=>{ handle(onResolved) }) }else if(_self.status==='rejected'){//當(dāng)前promise的狀態(tài)是rejected //異步執(zhí)行失敗的回調(diào) nextTick(()=>{ handle(onRejected) }) }else{ //當(dāng)前promise 的狀態(tài)是pending //將成功和失敗的回調(diào)函數(shù)保存到callback中去存起來 _self.callbacks.push({ onResolved(){ handle(onResolved); }, onRejected(){ handle(onRejected); } }) } })5、Promise.prototype.catch()方法的實(shí)現(xiàn)
/* Promise 原型對(duì)象的catch() 指定失敗的回調(diào)函數(shù) 返回一個(gè)promise對(duì)象*/Promise.prototype.catch=function(onRejected){ return this.then(null,onRejected)}????大功告成,其實(shí)主要是把then 方法實(shí)現(xiàn),其它的都好實(shí)現(xiàn)了,其它的方法大家試下吧。
四、最后
????這篇文章主要跟大家探討了瀏覽器異步編程的運(yùn)行機(jī)制還有Promise的實(shí)現(xiàn)原理。
????前端之路漫漫其修遠(yuǎn)兮,吾將上下而求索,與君共勉!
—— E N D ——
排版:chuanrui
總結(jié)
以上是生活随笔為你收集整理的c++ 异步下获取线程执行结果_前端异步编程的那些事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python获取历史双色球数据_你的梦想
- 下一篇: 黑马c++32期_【每日一考】第40期: