Javascript 計時器

Web Worker 的應用

你才單線程,你全家都單線程

FlyC

Overview

Bonus

break-point vs console panel

setTimout vs setInterval 的選擇

Web Worker

setTimout vs setInterval 的選擇

    // javascript


    // setTimeout
    var timeoutSec = 1,
        timeoutCounter = 1;

    setTimeout(function eachTime() {
        console.log('%c setTimeout: ' + timeoutCounter, 'color: blue');
        timeoutCounter++;

        setTimeout(eachTime, timeoutSec * 1000);
    }, timeoutSec * 1000);

    ////////////////////////////////////////////

    // setInterval
    var intervalSec = 1,
        intervalCounter = 1;

    setInterval(function eachTime() {
        console.log('%c setInterval: ' + intervalCounter, 'color: red');
        intervalCounter++;

    }, intervalSec * 1000);

setTimout vs setInterval 的選擇

在執行同步的funtion 時

兩者看不太出特別的差異

所以

setTimout vs setInterval 的選擇

// setTimeout
var timeoutSec = 1,
    timeoutCounter = 1;

setTimeout(function eachTime() {
    var message = '%c setTimeout: ' + timeoutCounter;
    console.log(message, 'color: blue');
    timeoutCounter++;

    // 模擬api 呼叫
    new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve();
        }, 3 * 1000);
    }).then(function() {
        console.log('api 回來囉!');
        // 等api 回來後再繼續執行
        setTimeout(eachTime, timeoutSec * 1000);
    });

}, timeoutSec * 1000);
// setInterval
var intervalSec = 1,
    intervalCounter = 1;

setInterval(function eachTime() {
    var message = '%c setInterval: ' + intervalCounter;
    console.log(message, 'color: red');
    intervalCounter++;

    // 模擬api 呼叫
    new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve();
        }, 3 * 1000);
    }).then(function() {
        console.log('api 回來囉!');
        // !! 糟糕,辦不到
    });

}, intervalSec * 1000);

setTimout vs setInterval 的選擇

看來setTimout 和setInterval 出現了點差距

setInterval 做不到在api 回來之後才繼續執行

 

那麼,

 

如果都執行同步的超複雜function 呢?

setTimout vs setInterval 的選擇

// javascript


// setTimeout
var timeoutSec = 1,
    timeoutCounter = 1;

setTimeout(function eachTime() {
    var message = '%c setTimeout: ' + timeoutCounter;
    console.log(message, 'color: blue');
    timeoutCounter++;

    setTimeout(eachTime, timeoutSec * 1000);

    // suuuuper long
    // 10w
    for (var i = 0; i < 10000000; i++);

}, timeoutSec * 1000);

// setInterval
var intervalSec = 1,
    intervalCounter = 1;

setInterval(function eachTime() {
    var message = '%c setInterval: ' + intervalCounter;
    console.log(message, 'color: red');
    intervalCounter++;

    // suuuuper long
    // 10w
    for (var i = 0; i < 10000000; i++);

}, intervalSec * 1000);

setTimout vs setInterval 的選擇

兩者還是沒有特別差異。

稍微做個總結:

1. setInterval 在開始執行之前會先「等待」

2. setTimeout 可以透過程式碼決定什麼時候執行下一次

那麼,來說個特例: 

setTimout vs setInterval 的選擇

// javascript


// setTimeout
var timeoutSec = 1,
    timeoutCounter = 1;

setTimeout(function eachTime() {
    var message = '%c setTimeout: ' + timeoutCounter;
    console.log(message, 'color: blue');
    timeoutCounter++;

    setTimeout(eachTime, timeoutSec * 1000);

    // suuuuper long
    // 1000w
    for (var i = 0; i < 10000000; i++);

}, timeoutSec * 1000);

// setInterval
var intervalSec = 1,
    intervalCounter = 1;

setInterval(function eachTime() {
    var message = '%c setInterval: ' + intervalCounter;
    console.log(message, 'color: red');
    intervalCounter++;

    // suuuuper long
    // 1000w
    for (var i = 0; i < 10000000; i++);

}, intervalSec * 1000);

請注意!

你的瀏覽器可能會崩潰

setTimout vs setInterval 的選擇

setTimeout 會等待上一次執行結束才執行

所以被卡住比較容易理解

 

setInterval 為什麼也會被卡住 ?

setTimout vs setInterval 的選擇

可以想像成,setInterval 的確把要執行的function 放到排隊隊伍裡了

由於每一個排隊的人的辦理時間都很長

所以

就算排隊隊伍人數「增加」的速度再快

都和辦理時間無關

 

只是一直在增加排隊隊伍而已

該怎麼辦呢 ?

增加辦理的窗口就好啦

就是Web Worker 啦

Web Worker

Web Worker ? 他能做什麼?

以非常簡單的方式說: 

它可以把事情放到背景執行

 

而且是把很多事情放到背景執行

 

接著會以dedicated worker 做介紹

Web Worker

javascript

好了,還你

API

Web Worker

我繼續做我的事情

這個好麻煩喔給你做

一個很難的工作

勤勞地忙碌中

很難的工作的結果

用那個結果來做事

...

好啦

總算完成囉

(開始思考自己是不是

工具人

Web Worker

不過我說

身為一個javascript

 

怎麼可能只丟一個工作給工具人呢

 

所以,實際上會比較像這樣: 

Web Worker

javascript

Web Worker

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

好啦

給你

給你

給你

給你

給你

給你

給你

給你

給你

Web Worker

不過我再說

身為一個javascript

 

怎麼可能只有一個工具人呢

 

所以,實際上更像是這樣: 

Web Worker

javascript

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

Web Worker

好難喔

Web Worker

這裡要特別注意的是


javascript 當然是可以自己下樓買早餐的

不過,這樣實在太累了

如果有人能把早餐買好送到樓下...

那不就更好了

Web Worker

還有要注意的是

雖然工具人Web Worker 很好用

 

如果只是非常簡單的工作

打電話給工具人呼叫Web Worker 的成本可能會更高

這時候

 

還是自己買早餐執行會比較快

Web Worker

我們來看看程式碼吧!

Web Worker

# Mac terminal
mkdir webWorker
cd webWorker
touch index.html
touch main.js
touch web-worker.js

# windows cmd
mkdir webWorker
cd webWorker
type nul > idnex.html
type nul > main.js
type nul > web-worker.js

創檔案

Web Worker

在各個檔案貼上

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Document</title>
    </head>
    <body>
    </body>
    <script src="main.js"></script>
</html>
// main.js
var webWorker = new Worker('./web-worker.js');

webWorker.postMessage('hello, ');

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log(receivedMessage);
}
// web-worker.js
var worker = this;

worker.onmessage = function(e) {
    var receivedMessage = e.data,
        sendMessage = receivedMessage + 'world!';

    worker.postMessage(sendMessage);
}

等等,還沒

Web Worker

Web Worker 會用到一個很特殊的方式: 

 

用javascript 呼叫javascript

 

所以我們得先創一個web-server 來做這件事情

推薦使用 http-server

簡單快速

#bash

#在剛剛創建的web-worker 資料夾內:
npm install --global http-server

#等待安裝完後
http-server

#用瀏覽器造訪出現的localhost 網址、開啟開發者視窗

Web Worker

如果看到這個,就成功囉

那麼,我們到底做了什麼呢?

先從main.js 開始看吧

Web Worker

// main.js
var webWorker = new Worker('./web-worker.js');

webWorker.postMessage('hello, ');

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log(receivedMessage);
}

從現在起

你就是我的工具人了

我打電話給你

說了 "hello, "

我等你的來電

那麼,工具人WebWorker那邊的狀況如何呢?

Web Worker

// web-worker.js
var worker = this;

worker.onmessage = function(e) {
    var receivedMessage = e.data,
        sendMessage = receivedMessage + 'world!';

    worker.postMessage(sendMessage);
}

收到電話很開心

努力工作

工作完成!

趕緊打電話回去

來張兩邊的完成圖吧

Web Worker

// main.js
var webWorker = new Worker('./web-worker.js');

webWorker.postMessage('hello, ');

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log(receivedMessage);
}
// web-worker.js
var worker = this;

worker.onmessage = function(e) {
    var receivedMessage = e.data,
        sendMessage = receivedMessage + 'world!';

    worker.postMessage(sendMessage);
}

從現在你你就是我的工具人了

打電話給你,快接

電話響了,趕快接

努力工作一波

工作完成! 打電話回去

工作做完了呢,很好

Web Worker

有了WebWorker 的幫忙

javascript 就有更多的時間可以做自己的事囉

 

你問時間在哪裡?

// main.js
var webWorker = new Worker('./web-worker.js');

webWorker.postMessage('hello, ');

/***
 在這裏
 ***/

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log(receivedMessage);
}

在這裏

Web Worker

言歸正傳,我們把setTimout 和setInterval 放進來吧

// main.js
var webWorker = new Worker('./web-worker.js'),
    style = 'color: #ff0063;'

var sec = 1,
    jobNumber = 1;
setTimeout(function eachTime() {
    setTimeout(eachTime, sec * 1000);

    webWorker.postMessage({
        message: '開始工作! 工作名稱: ' + jobNumber
    });

    jobNumber++;
}, sec * 1000);

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log('%cjavascript: 收到webWorker 的訊息囉!', style);
    console.log('%cjavascript: 收到的訊息是: ' + receivedMessage, style);
}

Web Worker

// web-worker.js
var worker = this,
    style = 'color: blue;'

worker.onmessage = function(e) {
    var message = e.data.message;

    console.log('');
    console.log('%c---------------------------', style);
    console.log('%cwebWorker 開始工作囉!', style);
    console.log('%cwebWorker 收到的訊息是: ' + message, style);

    console.log('%c努力工作中', style);
    // 努力工作
    for (var i = 0; i < 1000; i++);

    console.log('%c工作結束囉!', style);
    console.log('%c傳訊息給javascript: 我把' + message + ' 做完囉!', style);

    worker.postMessage('我把' + message + ' 做完囉!');
}

Web Worker

試著增加工作量吧!

Web Worker

// web-worker.js
var worker = this,
    style = 'color: blue;'

worker.onmessage = function(e) {
    var message = e.data.message;

    console.log('');
    console.log('%c---------------------------', style);
    console.log('%cwebWorker 開始工作囉!', style);
    console.log('%cwebWorker 收到的訊息是: ' + message, style);

    console.log('%c努力工作中', style);
    // 努力工作
    // 超負荷,超重loading
    for (var i = 0; i < 1000000000; i++) {
        if (i % 100000000 === 0) {
            console.log('%c我還活著! 我正在努力工作! 不要拋棄我QwQ!', style);
        }
    }

    console.log('%c工作結束囉!', style);
    console.log('%c傳訊息給javascript: 我把' + message + ' 做完囉!', style);

    worker.postMessage('我把' + message + ' 做完囉!');
}

放心跑,不會crash

Web Worker

他看起來快死了,暫停一下

Web Worker

我們發現

兩種顏色的訊息、

出現了 交錯 的現象

 

這正是最常見的多工 multi-thread 會出現的現象

 

那麼,怎麼證明呢 ?

怎麼證明在javascript 很忙沒時間看訊息的時候

WebWorker 沒有偷懶有在好好工作呢 ?

Web Worker

Alert 

不,這不是警告,這是window.alert 的意思

Web Worker

先讓我們回到必須自己買早餐的年代

Text

// main.js
// var webWorker = new Worker('./web-worker.js');
var style = 'color: #ff0063;';

var sec = 1,
    jobNumber = 1;
setTimeout(function eachTime() {
    setTimeout(eachTime, sec * 1000);

/*
    webWorker.postMessage({
        message: '開始工作! 工作名稱: ' + jobNumber
    });
*/
    for (var i = 0; i < 1000000000; i++) {
        if (i % 100000000 === 0) {
            console.log('%c我只能自己買早餐', style);
        }
    }
    console.log('%c早餐好ㄘ', style);

    jobNumber++;
}, sec * 1000);
/*
webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log('%cjavascript: 收到webWorker 的訊息囉!', style);
    console.log('%cjavascript: 收到的訊息是: ' + receivedMessage, style);
}
*/

Web Worker

我們的目標,是在買完10 份早餐之前

 

讓他停住!

 

你會,停不下來 (

Web Worker

反而是等到早餐買完才停下來呢

為什麼?

 

因為窗口很忙,沒時間理會 alert

 

那麼,有了WebWork 的情況又如何呢?

Web Worker

// main.js
var webWorker = new Worker('./web-worker.js');
var style = 'color: #ff0063;';

var sec = 1,
    jobNumber = 1;
setTimeout(function eachTime() {
    setTimeout(eachTime, sec * 1000);

    console.log('%c打電話給WebWorker', style);
    webWorker.postMessage({
        message: '幫我買早餐嘛!' + jobNumber
    });

    jobNumber++;
}, sec * 1000);

webWorker.onmessage = function(e) {
    var receivedMessage = e.data;

    console.log('%cjavascript: 早餐好ㄘ!', style);
}

Web Worker

// web-worker.js
var worker = this,
    style = 'color: blue;'

worker.onmessage = function(e) {
    var message = e.data.message;

    console.log('');
    console.log('%c---------------------------', style);
    console.log('%c電話響了! 收到的訊息是: ' + message, style);
    console.log('%cwebWorker 開始買早餐囉!', style);

    console.log('%c努力排隊中', style);
    // 努力工作
    // 超負荷,超重loading
    for (var i = 0; i < 1000000000; i++) {
        if (i % 100000000 === 0) {
            console.log('%c我還活著! 我正在努力排隊! 再等我一下QwQ!', style);
        }
    }

    console.log('%c總算買到囉!', style);
    console.log('%c來,這是早餐 ', style);

    worker.postMessage('我把早餐送來囉!');
}

Web Worker

Web Worker

出現了一堆 交叉

原因是

 

WebWorker 還在排隊的時候

javascript 就又打給他啦

 

每一秒,就會打一次的狂call

那麼

加上 alert 的操作會怎樣呢 ?

Web Worker

javascript 的紅色訊息停下來了

WebWorker 則是很盡責地繼續買...

也太淒涼了吧WebWorker!!

Web Worker

相信已經對WebWorker 有多好用盡責有充分瞭解了

不過之前說到的

 

身為一個javascript 

怎麼能只有一個工具人呢?

 

基於用javascript 呼叫javascript

該不會要創一大堆檔案吧?

 

不要,javascript 才不做這種事

這時候

Web Worker

Blob 就派上用場了

Web Worker

Blob? 誰啊

就是自己的工具人自己做啦

Web Worker

// main.js

// 不需要了!我自己做!
// var webWorker = new Worker('./web-worker.js');
var javascriptStyle = 'color: #ff0063;';

// WebWorker 製造機
function createWebWorker(webWorkerApplication) {
    webWorkerApplication = webWorkerApplication ? webWorkerApplication : Function.prototype;
    return new Worker(URL.createObjectURL(new Blob(['(' + webWorkerApplication + ')()'])));
}

// 工具人的人格
function webWorkerApplication() {
    var workerStyle = 'color: blue;';
    var worker = this;

    console.log('%c我被製造出來了!', workerStyle);
    worker.onmessage = function(e) {
        console.log('%c被製造出來的我接到電話了!', workerStyle);
        console.log('%c電話內容是: ' + e.data, workerStyle);

        console.log('%c被製造出來的我打電話回去!', workerStyle);
        worker.postMessage('是你製造了我嗎?');
    }
}

// 自己的webWorker 自己做!
var my_OWN_WebWorker = createWebWorker(webWorkerApplication);

console.log('%c發訊息給我自己做的webWorker!', javascriptStyle);
my_OWN_WebWorker.postMessage('我的子民們,聽命!!!');
my_OWN_WebWorker.onmessage = function(e) {
    console.log('%c我的子民回電了!', javascriptStyle);
    console.log('%c子民傳給我的內容是: ' + e.data, javascriptStyle);
}

// 參考資料: https://medium.com/@roman01la/run-web-worker-with-a-function-rather-than-external-file-303add905a0

Web Worker

以後要多少工具人WebWorker

就可以不用依賴靜態檔

 

想做多少自己做就好啦

Web Worker

還記得之前說過的

WebWorker 是要

 

用javascript 呼叫javascript

 

所以我們要用一個server 環境來支援WebWorker 嗎?

但在可以用Blob 達成自己製造WebWorker 的情況

應該就不需要server 的環境了對吧?

 

也就是可以把http-server 關掉了對吧?

Web Worker

你說的沒錯 !

Web Worker

值得一提的是

由javascript 自己產生的WebWorker 會以一個特別的形式出現在Network Pannel 的記錄裡

 

這就留到下次再討論吧

Web Worker

做個總結

  1. WebWorker 可以讓javascript 實作出多執行緒
  2. 一個javascript 可以有多個WebWorker
  3. 如果工作量太大,單一WebWorker 還是會卡死
  4. WebWorker 因迴圈卡死不會造成瀏覽器當機
  5. 可以透過javascript 程式碼而非實體檔產出WebWorker
  6. 一般的WebWorker 必須在有伺服器架構的環境下執行
  7. 透過Blob 產出的WebWorker 可以在靜態環境執行

Web Worker

說到工具人

 

你們有聽過

 

大家的工具人嗎

Shared Worker

shared worker

相較於專一的Dedicated worker

可以在多個頁面同時存在的Web Worker

 

 

也就是大家的工具人

 

不多作介紹,就先到這邊吧

Bonus

break-point vs console panel

除錯方法之

var message = 'hello, world!';
console.log(message);
message = 'hello, console!';
console.log(message);
var message = 'hello, world!';
debugger
// 手動輸入message
message = 'hello, debugger!';
debugger
// 手動輸入message

有什麼不一樣呢?

Bonus

break-point vs console panel

沒有

才怪,沒有我寫這個做什麼

Bonus

break-point vs console panel

var reference = {
    message: 'hello, world!'
};
console.log(reference);
reference.message = 'hello, console!';
console.log(reference);
var reference = {
    message: 'hello, world!'
};
debugger
// 手動輸入reference
reference.message = 'hello, debugger!';
debugger
// 手動輸入reference

那這樣呢?

Bonus

break-point vs console panel

不是啊何老師你騙我啊

你不是說不一樣嗎

Bonus

break-point vs console panel

杜先生

 

有把那個物件節點點開嗎?

Bonus

break-point vs console panel

恭迎慈孤觀音 渡世靈顯四方

Bonus

break-point vs console panel

... 但如果沒有先點開的話、就算是debugger 也還是會顯示call by reference 的結果喔

所以在除錯時得更加小心才是

The End

Javascript 計時器 與Web Worker

By flyc

Javascript 計時器 與Web Worker

  • 177