ES6(一) —— 異步編程解決辦法[從回調函數到promise,generator,async]

1、前言

異步編程對JavaScript來講很是重要,由於JavaScript的語言環境是單線程的,若是沒有異步編程將變得很是可怕,估計根本沒法使用。這篇文章就來總結一下從最原始的回調函數到如今的ES六、ES7的新方法。jquery

文章並不會具體介紹每種方法的原理,若是不是特別懂須要詳細瞭解的同窗能夠看阮一峯的ES6入門。阮大大介紹得很是具體,從原理到用法。es6

- 什麼是單線程?

單線程就是指進程中只有一個線程。單線程執行程序時,按照代碼的順序,上一個任務完成後纔會執行下一個任務。同一個時間只作一件事情。ajax

- 爲何JavaScript是單線程的?

JavaScript的主要做用就是操做DOM,若是兩段JS同時操做一個DOM,會引發渲染的衝突。因此JavaScript只能是單線程的。
HTML5中提出的Web Worker,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。編程

- 什麼是異步?

同步是指任務一件一件的按順序完成,上一件沒有完成就沒法作下一件;而異步則是指,開始作一件事以後就放在那兒等待結果,不須要守着,繼續作下一件事便可。json

異步能夠解決JavaScript單線程效率低、同一事件只能作一件事情的問題。api

console.log(1);
setTimeOut(()=>{
    console.log(2);
},1000)
console.log(3);

這段代碼首先會打印1,而後打印3,過1000ms之後打印2。promise

然而這段代碼內部是如何運行的呢,打印1和打印3的命令是同步命令,因此直接按順序放到主進程中執行,setTimeOut裏的是一個異步命令,在1000ms之後會被放入異步隊列中。而主進程會經過事件循環(event loop)不斷地從異步隊列中取出命令,而後執行。當主進程在1000ms之後查詢到了打印2的命令時,便把這個函數拿到主進程中執行。異步

2、異步編程的解決辦法

這裏所有用ajax連續調用例子,接口是豆瓣的真實接口,能夠獲得具體的數據,但有限制每小時150次。async

1.回調函數

這是最原始的一種異步解決方法。回調函數,就是指一件事作完之後,拿到結果後要作的事情。異步編程

var urlBase = 'https://api.douban.com/';
var start = 0,count = 5;
$.ajax({
    url: urlBase+'v2/book/user/1219073/collections',
    type: 'GET',
    dataType: 'jsonp',
    data:{
        start:start,
        count:count
    },
    success: function(data){
        console.log(data);
        start+=count;
        $.ajax({
            url: urlBase+'v2/book/user/1219073/collections',
            type: 'GET',
            dataType: 'jsonp',
            data:{
                start:start,
                count:count
            },
            success:function(data){
                console.log(data);
                start+=count;
                $.ajax({
                    url: urlBase+'v2/book/user/1219073/collections',
                    type: 'GET',
                    dataType: 'jsonp',
                    data:{
                        start:start,
                        count:count
                    },
                    success:function(data){
                        console.log(data);
                    }
                })
            }
        })
    }
})

這是用jquery的ajax方法調用的豆瓣某我的的收藏的圖書,start和count是豆瓣提供的接口參數,start表明從哪一條數據開始獲取,count表明一共獲取多少條數據。

從上面的代碼能夠看到多個回調函數的嵌套,若是須要調用得越多,回調也堆積得越多,多了之後代碼就很難維護,時間久了本身也要花好久才能看懂代碼。

改進辦法

將每一次回調的方法封裝成函數,代碼量會減小不少。

var urlBase = 'https://api.douban.com/';
var start = 0,count = 5;
function ajax(start,count,cb){
    $.ajax({
        url: urlBase+'v2/book/user/1219073/collections',
        type: 'GET',
        dataType: 'jsonp',
        data:{
            start:start,
            count:count
        },
        success:function(data){
            console.log(data);
            start+=count;
            cb && cb(start);
        }
    })

}

ajax(start,count,function(start){
    ajax(start,count,function(start){
        ajax(start,count)
    })
});

可是這樣依然沒有解決「回調地獄」的問題,當每次回調的邏輯操做變得愈來愈多的時候,代碼依然難以維護。

2.Promise(從jQuery的deferred對象演化而來)

Promise對象是ES6提出的一種對異步編程的解決方案,但它不是新的語法,而是一種新的寫法,容許將回調函數的嵌套改爲鏈式調用。

雖說Promise是ES6提出的標準,但其實jQuery在1.5版本之後就提出了相似的東西,叫作deferred對象。具體學習能夠看jQuery的deferred對象詳解

const urlBase = 'https://api.douban.com/';
let start = 0,count = 5;
function ajax(start,count){
    let dtd = $.Deferred();
    
    $.ajax({
        url: urlBase+'v2/book/user/1219073/collections',
        type: 'GET',
        dataType: 'jsonp',
        data:{
            start:start,
            count:count
        },
        success:function(data){
            start+=count;
            dtd.resolve(data,start);
        },
        error:function(err){
            dtd.reject(err);
        }
    })

    return dtd;
}


ajax(start,count).then((data1,start) => {
    console.log(data1);
    return ajax(start,count);
}).then((data2,start) => {
    console.log(data2);
    return ajax(start,count);
}).then((data3,start) => {
    console.log(data3);
}).catch((err) => {
    console.log('這裏出錯啦');
})

從這段代碼能夠看出來,寫法和promise很是類似了,能夠猜想promise就是從deferred演化而來的。

一樣的功能實現能夠改爲如下寫法:

const urlBase = 'https://api.douban.com/';
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+'v2/book/user/1219073/collections',
            type: 'GET',
            dataType: 'jsonp',
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data,start);
            },
            error:function(err){
                reject(err);
            }
        })
    })
}


ajax(start,count).then((data1,start) => {
    console.log(data1);
    return ajax(start,count);
}).then((data2,start) => {
    console.log(data2);
    return ajax(start,count);
}).then((data3,start) => {
    console.log(data3);
}).catch((err) => {
    console.log('這裏出錯啦');
})

Promise使用.then方法解決了回調的問題,但代碼依然冗餘,且語義不強,放眼望去全是.then方法,很難找出須要修改的地方。

3.Generator

Generator函數也是ES6中提出的異步編程解決方法,整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。最大特色就是能夠交出函數的執行權(即暫停執行)。
異步操做須要暫停的地方,都用yield語句註明。

const urlBase = 'https://api.douban.com/';
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+'v2/book/user/1219073/collections',
            type: 'GET',
            dataType: 'jsonp',
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data);
            },
            error:function(err){
                reject(err);
            }
        })
    })
    
}

let gen = function*(){
    yield ajax(start,count);
    start+=count;
    yield ajax(start,count);
    start+=count;
    yield ajax(start,count);
}


let g = gen();
g.next().value.then((data1) => {
    console.log(data1);
    g.next().value.then((data2) => {
        console.log(data2);
        g.next().value.then((data3) => {
            console.log(data3);
        })
    })
})

這樣在gen函數內三個ajax請求就看起來很是像同步的寫法了,可是執行的過程並不清晰,且須要手動.next來執行下一個操做。這並非咱們想要的完美異步方案。

4.async

async函數是ES7提出的一種異步解決方案,它與generator並沒有大的不一樣,並且能夠說它就是generator的一種語法糖。它的語法只是把generator函數裏的*換成了async,yield換成了await,但它同時有幾個優勢。

(1)內置執行器。這表示它不須要不停的next來使程序繼續向下進行。
(2)更好的語義。async表明異步,await表明等待。
(3)更廣的適用性。await命令後面能夠跟Promise對象,也能夠是原始類型的值。
(4)返回的是Promise。

const urlBase = 'https://api.douban.com/';
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+'v2/book/user/1219073/collections',
            type: 'GET',
            dataType: 'jsonp',
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data);
            },
            error:function(err){
                reject(err);
            }
        })
    })
    
}

async function getData(){
    let data = null;
    try{
        for(let i = 0;i < 3;i++){
            data = await ajax(start,count);
            console.log(data);
            start+=count;
        }
    }
    catch(err){
        console.log(err);
    }
}

getData();

用async函數改寫以後語義清晰,代碼量也減小了,而且內部自帶執行器,感受很符合想象中的異步解決方法。

3、結語

到此就把幾種常見的異步回調方法介紹完了,我我的感受用async+promise是最好的辦法。固然爲了更加深入的理解這些異步解決辦法,必定要多多的用到項目中,多用纔會多理解。

相關文章
相關標籤/搜索