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