JavaScript異步和單線程

一,同步和異步的區別:
同步會阻塞代碼執行,而異步不會。(好比alert是同步,setTimeout是異步)
二,前端使用異步的場景:
1,定時任務:setTimeout,setInterval
2,網絡請求:ajax請求,動態<img>加載
3,事件綁定
三,什麼是單線程,和異步有什麼關係
1,什麼是單線程:只有一個線程,同一時間只能作一件事情,從上到下執行
例:
    // 循環執行期間,JS 執行和DOM渲染暫時卡頓
    var i, sum = 0;
    for(var i = 0; i < 1000000000; i++) {
        sum += 1;
    }
    console.log(sum);

    // alert 不處理,JS 執行和DOM渲染暫時卡頓
    alert('hello');
    console.log(2);
2,爲何是單線程:避免DOM渲染的衝突:
(1),瀏覽器須要渲染DOM
(2),JS能夠修改DOM結構
(3),JS執行的時候,瀏覽器DOM渲染會暫停
(4),兩段JS也不能同時執行
(5),爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質
 
3,JS單線程解決方案:異步
    console.log(100)
    setTimeout(function() {
        console.log(200)   //反正1000ms 以後執行
    },1000)                //先無論它,先讓其餘JS代碼執行
    console.log(300);
var ajax = $.ajax({
        url: './data.json',
        success: function(result) {  //ajax加載完才執行
            console.log(result);     //先無論它,先讓其餘JS代碼執行
        }
    })
    console.log(ajax) //{readyState: 1, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
    console.log(200); //200 
1,異步的問題:
問題一,沒按照書寫方式順序執行,可讀性差
問題二,callback中不容易模塊化
四,什麼是event-loop(異步方案的實現方式)
event-loop:事件輪詢,JS實現異步的具體解決方案。
-同步代碼,直接執行
-異步函數先放在 異步隊列 中
-待同步函數執行完畢,輪詢執行 異步隊列 中的函數
(定時任務,網絡請求,事件綁定)
例1:
例2:
例3:
五,jquery的Deferred
1, jQuery1.5的變化:使用jQuery Deferred,初步引入Promise概念。
(1)仍不會也沒法改變js異步和單線程的本質
(2)只能從寫法上杜絕callback這種形式
(3)它是一種語法糖形式,可是解耦了代碼
(4)很好地體現:開放封閉原則(對擴展開放,對修改封閉)(多人開發協做不相互干擾,減小回歸測試的成本)
    // jQuery1.5以前
    var ajax = $.ajax({
        url: './data.json',
        success: function(result) {
            console.log('success1');
            console.log('success2');
            console.log('success3');
        },
        error:function(){
            console.log('error');
        }
    })
    console.log(ajax) //返回一個XHR對象
    // jquery1.5以後
    var ajax = $.ajax('./data.json');
    ajax.done(function() {
            console.log('success1');
        })
        .fail(function() {
            console.log('error1');
        })
        .done(function() {
            console.log('success2')
        })
    console.log(ajax); //返回一個deferred對象,能夠進行鏈式操做

    // 還可使用很像promise寫法
    var ajax = $.ajax('./data.json');
    ajax.then(function(){
        console.log('success1');
    },function(){
        console.log('error1');
    }).then(function(){
        console.log('success2');
    },function(){
        console.log('error2');
    })

2,jQuery Deferred應用css

看一個簡單的例子:html

   // 給出一段很是簡單的異步操做代碼,使用setTimeout函數
    var wait = function() {
        var task = function() {
            console.log('執行完成');
            // 1
            // 2
            // 3
        }
        setTimeout(task, 1000)
    }
    wait()
    // 新增需求:要在執行完成以後進行某些特別複雜的操做,代碼可能會不少,並且分好幾個步驟

使用Deferred:(開放封閉原則)前端

   function waitHandler() {
        // 定義
        var dtd = $.Deferred(); //建立一個deferred對象
        var wait = function(dtd) { //要求傳入一個deferred對象
            var task = function() {
                console.log('執行完成');
                // 成功
                dtd.resolve(); //表示異步任務已經完成
                // 失敗
                // dtd.reject();  //表示異步任務失敗或出錯
            }
            setTimeout(task, 1000)
            // wait返回
            return dtd; //要求返回deferred對象
        }
        // 最終返回  注意這裏必定要有返回值
        return wait(dtd);
    }

    var w = waitHandler();
    w.then(function() {
        console.log('ok1');
    }, function() {
        console.log('err1');
    }).then(function() {
        console.log('ok2');
    }, function() {
        console.log('err2');
    })

    // 還有 w.done  w.fail

執行rejectjquery

  function waitHandler() {
        // 定義
        var dtd = $.Deferred(); //建立一個deferred對象
        var wait = function(dtd) { //要求傳入一個deferred對象
            var task = function() {
                console.log('執行完成');
                // 成功
                // dtd.resolve(); //表示異步任務已經完成
                // 失敗
                dtd.reject(); //表示異步任務失敗或出錯
            }
            setTimeout(task, 1000)
            // wait返回
            return dtd; //要求返回deferred對象
        }
        // 最終返回  注意這裏必定要有返回值
        return wait(dtd);
    }

    var w = waitHandler();
    w.then(function() {
        console.log('ok1');
    }, function() {
        console.log('err1');
    })
    w.then(function() { //reject須要分開,否則執行順序就錯啦
        console.log('ok2');
    }, function() {
        console.log('err2');
    }) 

這裏注意一個原則:開放封閉原則ajax

總結,dtd的API可分紅兩類,用意不一樣npm

第一類(主動執行):dtd.resolve dtd.reject
第二類(監聽):dtd.then dtd.done dtd.fail
這兩類應該分開,不然後果很嚴重
可在上面代碼最後執行dtd.reject()試下結果json

使用dtd.promise()數組

  function waitHandler(){
        var dtd = $.Deferred();//Deferred 
        var wait = function(dtd) {
            var task = function(){
                console.log('執行完成');
                dtd.resolve();
            }
            setTimeout(task,2000);
            return dtd.promise();//注意,這裏返回的是promise,而不是直接返回deferred對象
        }
        return wait(dtd);
    }
    var w = waitHandler();//通過上面的改動,w接收的就是一個promise對象
    $.when(w)
    .then(function(){
        console.log('ok1');
    })
    .then(function(){
        console.log('ok1');
    })
    w.reject()//執行這句會報錯 w.reject is not a function  promise不能使用,只有監聽的方法了,使用者只能監聽,開發人員封裝的時候才能用

 

 說明promise和deferred區別: promise只能被動監聽,不能主動執行promise

六,Promise的基本使用和原理

1,基本語法回顧(備註:如今高級瀏覽器基本都支持promise,若是有些不支持,能夠在cdn上找,引入 bluebird )瀏覽器

<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>

 

   function loadImg(src) {
        const promise = new Promise(function(resolve, reject) { //new Promise實例
            var img = document.createElement('img');
            img.src = src;

            img.onload = function() {
                resolve(img);
            }
            img.onerror = function() {
                reject('圖片加載失敗');
            }
        });
        return promise; //返回 Promise實例
    }
    var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png";
    var result = loadImg(src); //Promise實例
    result.then(function(img) { //then監聽結果,成功時執行resolve(), 失敗時執行reject()
        console.log(1, img.width); //1 164
        return img;
    }, function(img) {
        console.log('failed');
        return img;
    }).then(function(img) {
        console.log(2, img.height) //2 36
    })

2,異常捕獲

    // 規定:then只接受一個參數(成功的處理函數),最後統一用catch捕獲異常
    // var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png";
    var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png";//寫一個不存在的地址
    var result = loadImg(src);
    result.then(function(img) {
        console.log(img.width);
        return img;
    }).then(function(img) {
        console.log(img.height);
    }).catch(function(ex) {
        // 最後統一用catch捕獲異常
        console.log(ex); //圖片加載失敗  (logo_1.png:1 GET https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png 404 (Not Found))
    })

自定義錯誤:

function loadImg(src) {
        const promise = new Promise(function(resolve, reject) { //new Promise實例
            var img = document.createElement('img');
            // 模擬拋出錯誤 模擬語法錯誤,邏輯以外的bug
            throw new Error('自定義錯誤');
            img.src = src;

            img.onload = function() {
                resolve(img);
            }
            img.onerror = function() {
                reject('圖片加載失敗');
            }
        });
        return promise; //返回 Promise實例
    }

     // 規定:then只接受一個參數(成功的處理函數),最後統一用catch捕獲異常
    var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png";
    // var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png"; //寫一個不存在的地址
    var result = loadImg(src);
    result.then(function(img) {
        console.log(img.width);
        return img;
    }).then(function(img) {
        console.log(img.height);
    }).catch(function(ex) {
        // 最後統一用catch捕獲異常
        console.log(ex); 
    })

3,多個串聯

   // 多個串聯 圖片只是用來模擬,實際可能更多的不是用圖片,好比處理用戶信息啥的,加載用戶信息的時候,先拿到用戶信息,再去處理其餘信息等
    var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png';
    var result1 = loadImg(src1);
    var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg';
    var result2 = loadImg(src2);

    // 鏈式操做
    result1.then(function(img1) {
        console.log('第一個圖片加載完成', img1.width);
        return result2; //重要!!!
    }).then(function(img2) {
        console.log('第二個圖片加載完成', img2.width);
    }).catch(function(ex) {
        // 最後統一用catch捕獲異常
        console.log(ex);
    })

4,Promise.all (所有promise實例完成後) 和 Promise.race (只要有一個promise實例完成)

   // Promise.all 接收一個包含多個promise實例的數組,待所有完成以後,統一執行success
    var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png';
    var result1 = loadImg(src1); //pending
    var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg';
    var result2 = loadImg(src2);
    Promise.all([result1, result2]).then(datas => {
        // 接收到的datas是一個數組,依次包含了多個promise返回的內容
        console.log('all', datas[0]); //all <img src=​"https:​/​/​shared-https.ydstatic.com/​dict/​v2016/​result/​logo.png">​
        console.log('all', datas[1]); //all <img src=​"https:​/​/​img.mukewang.com/​user/​57b98e990001351004400440-100-100.jpg">​
    })

Promise.race (只要有一個promise實例完成)

    // Promise.race 接收一個包含多個promise實例的數組
    // 只要有一個完成,就執行success
    Promise.race([result1, result2]).then(data => {
        // data即最早執行完成的promise的返回值
        console.log('race', data); //race <img src=​"https:​/​/​shared-https.ydstatic.com/​dict/​v2016/​result/​logo.png">​
    })

promise標準

1,關於「標準」的閒談

任何技術推廣使用都須要一套標準來支撐 
如html css js等,無規矩不成方圓
任何不符合標準的東西,終將會被用戶拋棄
不要挑戰標準,不要自造標準

2,狀態變化

三種狀態:pending fulfilled rejected
pending:   初始狀態
fulfilled: 成功
rejected:失敗

pending到fulfilled,或者pending到rejected,狀態不可逆

3,then

Promise實例必須實現then方法
then()必須能夠接收兩個函數做爲參數
then()返回的必須是一個Promise實例
若是then中沒有返回promise實例,則默認返回的是上一個promise實例,若是返回了一個promise實例,那就默認爲返回的promise實例

七,async/await(和promise區別、聯繫等)(babel已經支持,koa也已經支持)

1,then只是將callback拆分了

    // then只是將callback拆分了
    var w = waitHandler();
    w.then(function() {
        console.log('ok1');
    }, function() {
        console.log('err1');
    }).then(function() { 
        console.log('ok2');
    }, function() {
        console.log('err2');
    })

 


2,async/await是最直接的同步寫法

   // async/await是最直接的同步寫法
    const load = async function() {
        const result1 = await loadImg(src1);
        console.log(result1);
        const result2 = await loadImg(src2);
        console.log(result2);
    }
    load()

完整的:

    import 'babel-polyfill'; //引入babel-polyfill 

    // async/await是最直接的同步寫法
    function loadImg(src) {
        const promise = new Promise(function(resolve, reject) {
            var img = document.createElement('img');
            img.src = src;

            img.onload = function() {
                resolve(img);
            }
            img.onerror = function() {
                reject('圖片加載失敗');
            }
        });
        return promise;
    }
    var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png';
    var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg';
    // async/await是最直接的同步寫法
    const load = async function() { //函數必須用async標識
        const result1 = await loadImg(src1); //await後面跟的是一個Promise實例
        console.log(result1);
        const result2 = await loadImg(src2); //await後面跟的是一個Promise實例
        console.log(result2);
    }
    load()

 

用法:
要在函數體內使用await,函數必須用async標識
await後面跟的是一個Promise實例
須要安裝babel-polyfill

npm i --save-dev babel-polyfill 

3,總結

使用了Promise,並無和Promise衝突
徹底是同步的寫法,再也沒有回調函數
可是:改變不了js單線程,異步的本質

 
八,當前 JS 解決異步的方案總結

1,jquery Deferred2,Promise3,Async/await

相關文章
相關標籤/搜索