非同步處理就是依照不同步的程式處理問題。非同步處理與同步處理是對立的,而產生他們的是多執行緒或多進程。非同步處理的好處是提高設備使用率,從而在宏觀上提升程式運作效率,但是弊端就是容易出現衝突操作和資料髒讀。本文我們就和大家分享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中的异步处理,希望能帮助到大家。
相关推荐:
以上是JavaScript中的非同步處理解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!