JavaScript的sleep實現--Javascript非同步編程學習

來源:互聯網
上載者:User

標籤:c函數   ++   cond   ret   pre   alt   site   ntb   stack   

一、原始需求

最近在做百度前端技術學院的練習題,有一個練習是要求遍曆一個二叉樹,並且做遍曆可視化即正在遍曆的節點最好顏色不同

二叉樹大概長這個樣子:

以前序走訪為例啊,

每次訪問二叉樹的節點加個sleep就好了?

筆者寫出來是這樣的:

 1 let root = document.getElementById(‘root-box‘); 2  3   function preOrder (node) { 4         if (node === undefined) { 5             return; 6         } 7         node.style.backgroundColor = ‘blue‘;//開始訪問 8         sleep(500); 9         node.style.backgroundColor = ‘#ffffff‘;//訪問完畢10         preOrder(node.children[0]);11         preOrder(node.children[1]);12     }13 14     document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {15         preOrder(root);16     });

問題來了,JavaScript裡沒有sleep函數!

二、setTimeout實現

瞭解JavaScript的並行存取模型 EventLoop 的都知道JavaScript是單線程的,所有的耗時操作都是非同步

可以用setTimeout來類比一個非同步操作,用法如下:

 setTimeout(function(){ console.log(‘非同步作業執行了‘); },milliSecond); 

意思是在milliSecond毫秒後console.log會執行,setTimeout的第一個參數為回呼函數,即在過了第二個參數指定的時間後會執行一次。

如所示,Stack(棧)上是當前執行的函數調用棧,而Queue(訊息佇列)裡存的是下一個EventLoop迴圈要依次執行的函數。

實際上,setTimeout的作用是在指定時間後把回呼函數加到訊息佇列的尾部,如果隊列裡沒有其他訊息,那麼回調會直接執行。即setTimeout的時間參數僅表示最少多長時間後會執行。

更詳細的關於EventLoop的知識就不再贅述,有興趣的可以去瞭解關於setImmediate和Process.nextTick以及setTimeout(f,0)的區別

據此寫出了實際可啟動並執行可視化遍曆如下:

  let root = document.getElementById(‘root-box‘);    let count = 1;    //前序    function preOrder (node) {        if (node === undefined) {            return;        }        (function (node) {            setTimeout(function () {                node.style.backgroundColor = ‘blue‘;            }, count * 1000);        })(node);        (function (node) {            setTimeout(function () {                node.style.backgroundColor = ‘#ffffff‘;            }, count * 1000 + 500);        })(node);        count++;        preOrder(node.children[0]);        preOrder(node.children[1]);    } document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {        count = 1;        preOrder(root);    });

可以看出我的思路是把遍曆時的顏色改變全部變成回調,為了形成時間的差距,有一個count變數在隨遍曆次數遞增。

這樣看起來是比較清晰了,但和我最開始想像的sleep還是差別太大。

sleep的作用是阻塞當前進程一段時間,那麼好像在JavaScript裡是很不恰當的,不過還是可以類比的

三、Generator實現

 在學習《ES6標準入門》這本書時,依稀記得generator函數有一個特性,每次執行到下一個yield語句處,yield的作用正是把cpu控制權交出外部,感覺可以用來做sleep。

寫出來是這樣:

let root = document.getElementById(‘root-box‘);  function* preOrder (node) {        if (node === undefined) {            return;        }        node.style.backgroundColor = ‘blue‘;//訪問        yield ‘sleep‘;        node.style.backgroundColor = ‘#ffffff‘;//延時        yield* preOrder(node.children[0]);        yield* preOrder(node.children[1]);    }    function sleeper (millisecond, Executor) {        for (let count = 1; count < 33; count++) {            (function (Executor) {                setTimeout(function () {                    Executor.next();                }, count * millisecond);            })(Executor);        }    }    document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {        let preOrderExecutor = preOrder(root);        sleeper(500, preOrderExecutor);    });

這種代碼感覺很奇怪,相比於之前的setTimeout好像沒什麼改進之處,還是有一個count在遞增,而且必須事先指導遍曆次數,才能引導generator函數執行。

四、Generator+Promise實現

為了改進,讓generator能夠自動的按照500毫秒執行一次,藉助了Promise的resolve功能。使用thunk函數的回調來實現應該也是可以的,不過看起來Promise更容易理解一點

思路就是,每一次延時是一個Promise,指定時間後resolve,而resolve的回調就將Generator的指標移到下一個yield語句處。

  let root = document.getElementById(‘root-box‘);    function sleep (millisecond) {        return new Promise(function (resolve, reject) {            setTimeout(function () {                resolve(‘wake‘);            }, millisecond);        });    }    function* preOrder (node) {        if (node === undefined) {            return;        }        node.style.backgroundColor = ‘blue‘;//訪問        yield sleep(500);//返回了一個promise對象        node.style.backgroundColor = ‘#ffffff‘;//延時        yield* preOrder(node.children[0]);        yield* preOrder(node.children[1]);    }    function executor (it) {        function runner (result) {            if (result.done) {                return result.value;            }            return result.value.then(function (resolve) {                runner(it.next());//resolve之後調用            }, function (reject) {                throw new Error(‘useless error‘);            });        }        runner(it.next());    }    document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {        let preOrderExecutor = preOrder(root);        executor(preOrderExecutor);    });

看起來很像原始需求提出的sleep的感覺了,不過需要自己寫一個Generator的執行器

五、Async實現

ES更新的標準即ES7有一個async函數,async函數內建了Generator的執行器,只需要自己寫generator函數即可

let root = document.getElementById(‘root-box‘);  function sleep (millisecond) {        return new Promise(function (resovle, reject) {            setTimeout(function () {                resovle(‘wake‘);            }, millisecond);        });    }    async function preOrder (node) {        if (node === undefined) {            return;        }        let res = null;        node.style.backgroundColor = ‘blue‘;        await sleep(500);        node.style.backgroundColor = ‘#ffffff‘;        await preOrder(node.children[0]);        await preOrder(node.children[1]);    }    document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {        preOrder(root);    });

大概只能做到這一步了,sleep(500)前面的await指明了這是一個非同步操作。

不過我更喜歡下面這種寫法:

 let root = document.getElementById(‘root-box‘);    function visit (node) {        node.style.backgroundColor = ‘blue‘;        return new Promise(function (resolve, reject) {            setTimeout(function () {                node.style.backgroundColor = ‘#ffffff‘;                resolve(‘visited‘);            }, 500);        });    }    async function preOrder (node) {        if (node === undefined) {            return;        }await visit(node);        await preOrder(node.children[0]);        await preOrder(node.children[1]);    }    document.getElementById(‘pre-order‘).addEventListener(‘click‘, function () {        preOrder(root);    });

不再糾結於sleep函數的實現了,visit更符合現實中的情景,訪問節點是一個耗時的操作。整個代碼看起來清晰易懂。

經過這次學習,體會到了JavaScript非同步思想,所以,直接硬套C語言的sleep的概念是不合適的,JavaScript的世界是非同步世界,而async出現是為了更好的組織非同步代碼的書寫,思想仍是非同步

 

在下初出茅廬,文章中有什麼不對的地方還請不吝賜教

參考文獻:

1、《ES6標準入門》

2、JavaScript並行存取模型與Event Loop:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop

JavaScript的sleep實現--Javascript非同步編程學習

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.