首頁  >  文章  >  web前端  >  JavaScript中的非同步處理解析

JavaScript中的非同步處理解析

小云云
小云云原創
2017-12-04 11:32:121663瀏覽

非同步處理就是依照不同步的程式處理問題。非同步處理與同步處理是對立的,而產生他們的是多執行緒或多進程。非同步處理的好處是提高設備使用率,從而在宏觀上提升程式運作效率,但是弊端就是容易出現衝突操作和資料髒讀。本文我們就和大家分享JavaScript中的非同步處理。

在 JavaScript 的世界中,所有程式碼都是單執行緒執行的。由於這個“缺陷”,導致 JavaScript 的所有網頁操作,瀏覽器事件,都必須是非同步執行。非同步執行可以用回呼函數實作

非同步操作會在將來的某個時間點觸發一個函數呼叫

主流的非同步處理方案主要有:回呼函數(CallBack) 、 Promise 、 Generator函數、 async/await 。

一、回呼函數(CallBack)

這是非同步程式設計最基本的方法

假設我們有一個getData 方法,用於非同步取得數據,第一個參數為請求的url 位址,第二個參數是回呼函數,如下:

function getData(url, callBack){    // 模拟发送网络请求
    setTimeout(()=> {        // 假设 res 就是返回的数据
        var res = {
            url: url,
            data: Math.random()
        }        // 执行回调,将数据作为参数传递
        callBack(res)
    }, 1000)
}

我們預先設定一個場景,假設我們要請求三次伺服器,每一次的請求依賴上次請求的結果,如下:

getData('/page/1?param=123', (res1) => {    console.log(res1)
    getData(`/page/2?param=${res1.data}`, (res2) => {        console.log(res2)
        getData(`/page/3?param=${res2.data}`, (res3) => {            console.log(res3)
        })
    })
})

透過上面的程式碼可以看出,第一次請求的url 位址為: /page/1?param=123 ,回傳結果為res1 。

第二個請求的 url 位址為: /page/2?param=${res1.data} ,依賴第 一次請求的 res1.data ,回傳結果為 res2`。

第三次請求的 url 位址為: /page/3?param=${res2.data} ,依賴第二次請求的 res2.data ,回傳結果為 res3 。

由於後續請求依賴前一個請求的結果,所以我們只能把下一次請求寫到上一次請求的回調函數內部,這樣就形成了常說的:回調地獄。

二、發布/訂閱

我們假定,存在一個”信號中心”,某個任務執行完成,就向信號中心”發布”( publish )一個信號,其他任務可以向訊號中心」訂閱」( subscribe )這個訊號,從而知道什麼時候自己可以開始執行。這叫做」發布/訂閱模式」(publish-subscribe pattern),又稱」觀察者模式」(observer pattern)

這個模式有多種實現,以下採用的是Ben Alman的 Tiny Pub/ Sub ,這是jQuery 的一個插件

首先, f2 向」訊號中心」 jQuery 訂閱」 done 「訊號

jQuery.subscribe("done", f2);
f1进行如下改写
function f1(){                setTimeout(function(){                        // f1的任务代码                        jQuery.publish("done");                }, 1000);}
jQuery.publish("done") 的意思是, f1 执行完成后,向”信号中心 "jQuery 发布 "done" 信号,从而引发f2的执行。 此外,f2完成执行后,也可以取消订阅( unsubscribe )
jQuery.unsubscribe("done", f2);

這種方法的性質與」事件監聽」類似,但是明顯優於後者。因為我們可以透過查看”訊息中心”,了解存在多少訊號、每個訊號有多少訂閱者,從而監控程式的運作。

三、Promise

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回呼函數和事件——更合理和更強大

所謂Promise ,簡單說就是一個容器,裡面保存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上來說, Promise 是一個對象,從它可以取得非同步操作的訊息。 Promise 提供統一的API ,各種非同步操作都可以用同樣的方法進行處理

簡單說,它的思想是,每一個非同步任務返回一個Promise 對象,該對像有一個then 方法,允許指定回調函數。

現在我們使用Promise 重新實作上面的案例,首先,我們要把非同步請求資料的方法封裝成Promise

function getDataAsync(url){    return new Promise((resolve, reject) => {
        setTimeout(()=> {            var res = {
                url: url,
                data: Math.random()
            }
            resolve(res)
        }, 1000)
    })
}

那麼請求的程式碼應該這樣寫

getDataAsync('/page/1?param=123')
    .then(res1=> {        console.log(res1)        return getDataAsync(`/page/2?param=${res1.data}`)
    })
    .then(res2=> {        console.log(res2)        return getDataAsync(`/page/3?param=${res2.data}`)
    })
    .then(res3=> {        console.log(res3)
    })

then 方法傳回一個新的Promise 對象, then 方法的鍊式呼叫避免了CallBack 回呼地獄

但也不是完美,例如我們要加入很多then 語句, 每一個then 還是要寫一個回呼。

如果場景再複雜一點,例如後邊的每一個請求依賴前面所有請求的結果,而不只依賴上一次請求的結果,那會更複雜。 為了做的更好, async/await 就應運而生了,來看看使用async/await 要如何實現

四、async/await

getDataAsync 方法不變,如下

function getDataAsync(url){    return new Promise((resolve, reject) => {
        setTimeout(()=> {            var res = {
                url: url,
                data: Math.random()
            }
            resolve(res)
        }, 1000)
    })
}

商業代碼如下

async function getData(){    var res1 = await getDataAsync('/page/1?param=123')    console.log(res1)    var resData2 = await `sy( /page/2?param=${res1.data}`)    console.log(res2)    var res3 = await getDataAsync(`/page/2?param=${res2.data}`)    console.log(res3)
}

可以看到使用async\await 就像寫同步程式碼一樣

對比Promise 感覺怎麼樣?是不是非常清晰,但是async/await 是基於Promise 的,因為使用async 修飾的方法最終返回一個Promise , 實際上, async/await 可以看做是使用Generator 函數處理異步的語法糖,我們來看看如何使用Generator 函數處理非同步

五、Generator

首先非同步函數依然是

function getDataAsync(url){    return new Promise((resolve, reject) => {
        setTimeout(()=> {            var res = {
                url: url,
                data: Math.random()
            }
            resolve(res)
        }, 1000)
    })
}

使用Generator 函數可以這樣寫

function*getData(){    var res1 = yield getDataAsync('/page/1?param=123')   
 console.log(res1)    var res2 = yield getDataAsync(`/page/2?param=${res1.data}`)   
  console.log(res2)    var res3 = yield getDataAsync(`/page/2?param=${res2.data}`)    console.log(res3))
}

然後我們這樣逐步執行

var g = getData()
g.next().value.then(res1=> {
    g.next(res1).value.then(res2=> {
        g.next(res2).value.then(()=> {
            g.next()
        })
    })
})

上面的代码,我们逐步调用遍历器的 next() 方法,由于每一个 next() 方法返回值的 value 属性为一个 Promise 对象

所以我们为其添加 then 方法, 在 then 方法里面接着运行 next 方法挪移遍历器指针,直到 Generator 函数运行完成,实际上,这个过程我们不必手动完成,可以封装成一个简单的执行器

function run(gen){    var g = gen()    function next(data){        var res = g.next(data)        if (res.done) return res.value
        res.value.then((data) => {
            next(data)
        })
    }
    next()
}

run 方法用来自动运行异步的 Generator 函数,其实就是一个递归的过程调用的过程。这样我们就不必手动执行 Generator 函数了。 有了 run 方法,我们只需要这样运行 getData 方法

run(getData)

这样,我们就可以把异步操作封装到 Generator 函数内部,使用 run 方法作为 Generator 函数的自执行器,来处理异步。其实我们不难发现, async/await 方法相比于 Generator 处理异步的方式,有很多相似的地方,只不过 async/await 在语义化方面更加明显,同时 async/await 不需要我们手写执行器,其内部已经帮我们封装好了,这就是为什么说 async/await 是 Generator 函数处理异步的语法糖了

以上内容就是关于JavaScript中的异步处理,希望能帮助到大家。

相关推荐:

PHP异步处理的实现方案

详谈 Jquery Ajax异步处理Json数据

php 异步处理

以上是JavaScript中的非同步處理解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn