重新認識javascript的settimeout和異步

2021-01-07 IT168

  【IT168 技術】今晚看到QLeelulu的一道JavaScript面試題(setTimeout),稍微想了一下,好不容易連猜帶蒙,湊巧說對了答案。但是原因到底是什麼呢?自己一時也說不太清楚,反正感覺就是一個死循環造成的。然後看了一下文章下面的評論,發現5樓和6樓的回答很有道理,主要意思就是說javascript引擎是單線程執行的,while循環那裡執行的時候,settimeout裡面的函數根本沒有執行的機會,這樣while那裡永遠為真,造成死循環。但是單純看還是不怎麼踏實,最後發揮實踐精神,自己動手做了兩個實驗:

  1、簡單的settimeout

  setTimeout(function () { while (true) { } }, 1000);

  setTimeout(function () { alert('end 2'); }, 2000);

  setTimeout(function () { alert('end 1'); }, 100);

  alert('end');

  執行的結果是彈出『end』『end 1』,然後瀏覽器假死,就是不彈出『end 2』。也就是說第一個settimeout裡執行的時候是一個死循環,這個直接導致了理論上比它晚一秒執行的第二個settimeout裡的函數被阻塞,這個和我們平時所理解的異步函數多線程互不幹擾是不符的。

  2、ajax請求回調

  接著我們來測試一下通過xmlhttprequest實現ajax異步請求調用,主要代碼如下:



        var xmlReq = createXMLHTTP();//創建一個xmlhttprequest對象
        function testAsynRequest() {
            var url = "/AsyncHandler.ashx?action=ajax";
            xmlReq.open("post", url, true);
            xmlReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            xmlReq.onreadystatechange = function () {
                if (xmlReq.readyState == 4) {
                    if (xmlReq.status == 200) {
                        var jsonData = eval('(' + xmlReq.responseText + ')');
                        alert(jsonData.message);
                    }
                    else if (xmlReq.status == 404) {
                        alert("Requested URL is not found.");
                    } else if (xmlReq.status == 403) {
                        alert("Access denied.");
                    } else {
                        alert("status is " + xmlReq.status);
                    }
                }
            };
            xmlReq.send(null);
        }
        testAsynRequest();//1秒後調用回調函數
        
        while (true) {

        }
在服務端實現簡單的輸出:

        private void ProcessAjaxRequest(HttpContext context)
        {
            string action = context.Request["ajax"];
            Thread.Sleep(1000);//等1秒
            string jsonObject = "{\"message\":\"" + action + "\"}";
            context.Response.Write(jsonObject);
        }

  理論上,如果ajax異步請求,它的異步回調函數是在單獨一個線程中,那麼回調函數必然不被其他線程」阻撓「而順利執行,也就是1秒後,它回調執行彈出『ajax』,可是實際情況並非如此,回調函數無法執行,因為瀏覽器再次因為死循環假死。

  結論:

        根據實踐結果,可以得出,javascript引擎確實是單線程處理它的任務隊列(能理解成就是普通函數和回調函數構成的隊列嗎?)的。在javascript裡實現異步編程很大程度上就是一種障眼法,單線程的引擎實現多線程的編程,如果要實現一些資源同步互斥之類的操作(一如C#、Java等語言的多線程),我感覺真正實現起來根本無法輕易得到保證。

  補充:如何實現javascript的sleep呢?在stackoverflow上找到一篇javascript sleep,試了一下,效果是有了,但是執行的時候cpu很高,真還不如直接settimeout呢。

相關焦點

  • setTimeout 的黑魔法
    這個過程中,js並不會阻塞代碼等待其他線程執行完畢,而且其他線程執行完畢後添加事件任務告訴js引擎執行相關操作.這就是js的異步編程模型.如此我們再回過頭來看settimeout(0)就會恍然大悟.js代碼執行到這裡時,會開啟一個定時器線程,然後繼續執行下面的代碼.該線程會在指定時間後往事件隊列裡面插入一個任務.由此可知settimeout(0)裡面的操作會放在所有主線程任務之後.
  • 面試官:為什麼 Promise 比setTimeout() 快?
    2.事件循環與異步 JS 相關的問題可以通過研究事件循環來回答。我們回顧一下異步 JS 工作方式的主要組成部分。調用堆棧是一個LIFO(後進先出)結構,它存儲在代碼執行期間創建的執行上下文。Web api是異步操作(fetch 請求、promise、計時器)及其回調等待完成的地方。**task queue (任務隊列)是一個FIFO(先進先出)**結構,它保存準備執行的異步操作的回調。
  • 從setTimeout(fn,0)函數剖析JavaScript的執行機制
    進程和線程進程:是系統進行資源分配和調度的基本單位,是一個具有一定獨立功能的程序的一次運行活動,是程序的執行實例,包括程序計數器,寄存器和變量的當前值;多進程:啟動多個進程,而每啟動一個進程,都要分配給他獨立的地址空間,建立眾多的數據表來維護,這樣會造成很多浪費
  • JavaScript 異步與 Promise 實現
    假如某一天,比如幾個月後,線上出了問題,我們需要跟蹤異步流,找出問題所在,而跟蹤這類異步流,不僅需要理清個異步任務執行順序,還需要在眾多回調函數中不斷地跳躍,調試(或許你還能記得諸如 funcB這些函數的作用和實現),無論是出於效率,可讀性,還是出於人性化,都不希望開開發者們再經歷這種痛苦。
  • JavaScript同步、異步、回調執行順序之經典閉包setTimeout面試題分析
    大家注意了,教大家一道口訣:同步優先、異步靠邊、回調墊底(讀起來不順)用公式表達就是:同步 => 異步 => 回調這口訣有什麼用呢?用來對付面試的。記住我們的口訣 同步 => 異步 => 回調1、for循環和循環體外部的console是同步的,所以先執行for循環,再執行外部的console.log。(同步優先)2、for循環裡面有一個setTimeout回調,他是墊底的存在,只能最後執行。(回調墊底)那麼,為什麼我們最先輸出的是5呢?
  • JavaScript異步編程之jsdeferred原理解析
    _prev_timeout_called < 150){ var cancel = false; // 因為readyState會一直變化,避免重複執行 var script = document.createElement("script"); script.type = "text/javascript"; // 發送一個錯誤的url,快速觸發回調,實現異步操作 script.src =
  • JavaScript 執行機制
    因此聰明的程式設計師將任務分為兩類:當我們打開網站時,網頁的渲染過程就是一大堆同步任務,比如頁面骨架和頁面元素的渲染。而像加載圖片音樂之類佔用資源大耗時久的任務,就是異步任務。關於這部分有嚴格的文字定義,但本文的目的是用最小的學習成本徹底弄懂執行機制,所以我們用導圖來說明:
  • javascript優雅的異步處理爬蟲
    items.length >=1) {items.each((index, item) =>{let it = $(item)let num = it.find('.num').text()let bookname = $(it.find('.bookname a')[0]).text() // 使用$重新包裝元素
  • JavaScript 異步編程的終極演變
    下面將具體講解異步編程的原理和值得注意的地方,待我細細道來~異步編程的演變基本理解所謂異步,簡單地說就是一個任務分成兩段,先執行第一段,然後轉而執行其他任務,等做好準備再回過頭執行第二段。舉個例子讀取一個文件進行處理,任務的第一段是向作業系統發出請求,要求讀取文件。
  • 這一次,徹底弄懂 JavaScript 執行機制
    不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發工作,我們經常會遇到這樣的情況:給定的幾行代碼,我們需要知道其輸出內容和順序。因此聰明的程式設計師將任務分為兩類:同步任務異步任務當我們打開網站時,網頁的渲染過程就是一大堆同步任務,比如頁面骨架和頁面元素的渲染。
  • JavaScript異步與Promise實現
    假如某一天,比如幾個月後,線上出了問題,我們需要跟蹤異步流,找出問題所在,而跟蹤這類異步流,不僅需要理清個異步任務執行順序,還需要在眾多回調函數中不斷地跳躍,調試(或許你還能記得諸如funcB這些函數的作用和實現),無論是出於效率,可讀性,還是出於人性化,都不希望開開發者們再經歷這種痛苦。
  • 重新認識JavaScript中的true和false
    今天優優推薦的這篇譯文,就會對truthy和falsy進行詳細講解,希望對大家有幫助。真值和假值下面這些值在JavaScript中都是falsy:除上面幾個值以外,其他所有值都是truthy,包括字符串"0", "false", 空函數,空數組,空對象。var a = !!(0); var b = !!
  • 從promise讀懂JavaScript異步編程
    從《setTimeout(fn,0)函數剖析JavaScript的執行機制》一文已經說明了同步和異步的區別,而這篇文章將更深入的去理解什麼是JS的異步編程,以及promise,async,await等的使用。從而更好的為前端編程打好堅實的基礎。
  • JavaScript與異步·第一講—異步:何處惹塵埃
    自JavaScript誕生之日起,頻繁與異步打交道便是這門語言的使命,並為此衍生出了許多設計和理念。因此,深入理解異步的概念對於前端工程師來說極為重要。什麼是異步?程序是分「塊」執行的。最常見的「塊」是函數。
  • 拆解 JavaScript 中的異步模式
    我最初接觸不同的異步模式時,曾想當然的覺得 promise 就是比 callback 好, async await 比就是比 promise 優雅,會把它們割裂起來看待。後來發現也不完全這樣,各種異步模式之間其實存在著關聯,也有著各自擅長的場景。這段時間看了很多異步相關的資料(見文後的參考文獻),覺得對 JS 中的異步有了全新的認識,寫作本文已做梳理。
  • 你會用setTimeout嗎
    再加一個題目,只有下面代碼setTimeout(function () {    func1();}, 0)func2();func1和func2誰會先執行?這個答案應該比較簡單,func2先執行,func1後面執行。
  • 理解異步之美--- Promise與async await(一)
    如果表達有誤的地方,還望評論區指出~不多嗶嗶,坐穩扶好,發車了~你可能會放出一個怪物異步與同步相比,最難以掌控的就是異步的任務會什麼時候完成和完成之後的回調問題。難以掌控的觸發狀態,讓你自己寫的代碼當時還可以讀懂,但是過幾天半個月之後如果不重新盤一邊邏輯,你哪知道哪個內容會先執行借用這麼一個例子首先 執行listern()
  • 【JavaScript】Promise函數的用法
    javascript
  • 大前端進擊之路(二):JavaScript異步編程
    為了解決這個問題JS給出了兩種執行模式:同步模式(Synchronous)和異步模式(Asynchronous)。程序執行的順序和代碼編寫的順序是完全一致的。在單線程模式下,大多數任務都是以同步模式執行。異步模式上個例子中我們在等待水燒開的過程中什麼都沒幹,很浪費時間,我們可以在燒水的過程中將食材都準備好,等到水燒開後直接放入。
  • 從setTimeout/setInterval看JS線程
    初識setTimeout 與 setInterval先來簡單認識,後面我們試試用setTimeout 實現 setInterval 的功能setTimeout 延遲一段時間執行一次 (Only one)setTimeout(function, milliseconds, param1, param2, ...)