摘要:接下來,我們一起來看看中的異步編程,具體有哪幾種。實現異步編程的方法一回調函數上面不止一次提到了回調函數。它是異步編程中,最基本的方法。四對象接下來,我們聊聊與相關的異步編程方法,對象。
前言
最近,小伙伴S 問了我一段代碼:
const funB = (value) => { console.log("funB "+ value); }; const funA = (callback) => { ... setTimeout(() => { typeof callback === "function" && callback("is_ok!"); }, 1000); } funA(funB);
他不太理解這段代碼中,funB 函數作為 funA 函數的參數這樣的寫法。從語義上看,callback 的意思是回調,那么是說 funB 是 funA 的回調嘛?
我給他解釋說,funB 函數的確是 funA 函數的回調,它會等待 funA 中前面的語句都執行完,再去執行。這是一種異步編程的寫法。
小伙伴S 還是有點不太理解:異步編程是什么?除了回調函數之外,異步編程還有哪些?
別急,讓我們先從概念入手,再逐個理解異步編程中的方法,看看它的前世今生。
什么是異步?所謂"異步"(Asynchronous),可以理解為一種不連續的執行。簡單地說,就是把一個任務分成兩段,先執行第一段,然后轉而執行其他任務,等接到通知了,再回過頭執行第二段。
我們都知道,JavaScript是單線程的。而異步,對于JavaScript的重要性,則體現在非阻塞這一點上。一些常見的異步有:
onclick 在其事件觸發的時候,回調會立即添加到任務隊列中。
setTimeout 只有當時間到達的時候,才會將回調添加到任務隊列中。
ajax 在網絡請求完成并返回之后,才將回調添加到任務隊列中。
接下來,我們一起來看看Javascript中的異步編程,具體有哪幾種。
實現異步編程的方法 一、回調函數上面不止一次提到了回調函數。它從概念上說很簡單,就是把任務的第二段多帶帶寫在一個函數里面,等到重新執行這個任務的時候,就直接調用這個函數。它是異步編程中,最基本的方法。
舉個例子,假定有兩個函數 f1 和 f2,后者等待前者的執行結果。順序執行的話,可以這樣寫:
f1(); f2();
但是,如果 f1 是一個很耗時的任務,該怎么辦?
改寫一下 f1,把 f2 寫成 f1 的回調函數:
const f1 = (callback) => { setTimeout(() => { typeof callback === "function" && callback(); }, 1000); } f1(f2);二、事件監聽
onclick 的寫法,在異步編程中,稱為事件監聽。它的思路是:如果任務的執行不取決于代碼的順序,而取決于某個事件是否發生,也就事件驅動模式。
還是 f1 和 f2 的例子,為了簡化代碼,這里采用jQuery的寫法:
// 為f1綁定一個事件,當f1發生done事件,就執行f2 f1.on("done", f2); // 改寫f1 function f1(){ setTimeout(() => { // f1的任務代碼,執行完成后,立即觸發done事件 f1.trigger("done"); }, 1000); }
它的優點是:比較容易理解,耦合度降低了。可以綁定多個事件,而且每個事件還能指定多個回調函數。
缺點是:整個程序都會變為由事件來驅動,流程會變得很不清晰。
三、發布/訂閱這是一種為了處理一對多的業務場景而誕生的設計模式,它也是一種異步編程的方法。vue中MVVM的實現,就有它的功勞。
關于概念,我們可以這樣理解,假定存在一個"信號中心",某個任務執行完成,就向信號中心"發布"(publish)一個信號,其他任務可以向信號中心"訂閱"(subscribe)這個信號,從而知道什么時候自己可以開始執行。這就叫做"發布/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。
下面的例子,采用的是 Morgan Roderick 的 PubSubJS ,這是一個無依賴的JavaScript插件:
import PubSub from "pubsub-js"; // f2向 "PubSub" 訂閱信號 "done" PubSub.subscribe("done", f2); const f1 = () => { setTimeout(() => { // f1執行完成后,向 "PubSub" 發布信號 "done",從而執行 f2 PubSub.publish("done"); }, 1000); }; f1(); // f2 完成執行后,也可以取消訂閱 PubSub.unsubscribe("done", f2);
這種模式有點類似于“事件監聽”,但是明顯優于后者。因為,我們可以通過查看“消息中心”,了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。
四、Promise對象接下來,我們聊聊與ajax相關的異步編程方法,Promise對象。
Promise 是由 CommonJS 提出的一種規范,它是為了解決回調函數嵌套,也就是回調地獄的問題。它不是新的語法功能,而是一種新的寫法,允許將回調函數的橫向加載,改成縱向加載。它的思想是,每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數。
繼續改寫 f1 和 f2:
const f1 = () => { return new Promise((resolve, reject) => { let timeOut = Math.random() * 2; setTimeout(() => { if (timeOut < 1) { resolve("200 OK"); } else { reject("timeout in " + timeOut + " seconds."); } }, 1000); }); }; const f2 = () => { console.log("start f2"); }; f1().then((result) => { console.log(result); f2(); }).catch((reason) => { ... );
例子中,用隨機數模擬了請求的超時。當 f1 返回 Promise 的 resolve 時,執行 f2。
Promise的優點是:回調函數變成了鏈式的寫法,程序的流程可以看得很清楚。還有就是,如果一個任務已經完成,再添加回調函數,該回調函數會立即執行。所以,你不用擔心是否錯過了某個狀態。
缺點就是:編寫和理解,都相對比較難。
五、Generatorgenerator(生成器)是 ES6 標準引入的數據類型。它最大特點,就是可以交出函數的執行權(即暫停執行),是協程在 ES6 中的實現。
看上去它像一個函數,定義如下:
function* gen(x) { var y = yield x + 2; return y; }
它不同于普通函數,函數名之前要加星號(*),是可以暫停執行的。
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。用 yield 語句注明異步操作需要暫停的地方。
我們來看一下 Generator 函數執行的過程:
var g = gen(1); // { value: 3, done: false } g.next(); // { value: undefined, done: true } g.next();
上面代碼中,調用 Generator 函數,會返回一個內部指針(即遍歷器 )g 。這是 Generator 函數不同于普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針 g 的 next 方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的 yield 語句,上例是執行到 x + 2 為止。
換言之,next 方法的作用是分階段執行 Generator 函數。每次調用 next 方法,會返回一個對象,表示當前階段的信息( value 屬性和 done 屬性)。value 屬性是 yield 語句后面表達式的值,表示當前階段的值;done 屬性是一個布爾值,表示 Generator 函數是否執行完畢,即是否還有下一個階段。
六、async/await這是 ES8 中提出的一種更優雅的異步解決方案,靈感來自于 C# 語言。具體可前往 細說 async/await 相較于 Promise 的優勢 ,深入理解其原理及特性。
來看個例子,要實現一個暫停功能,輸入 N 毫秒,則停頓 N 毫秒后才繼續往下執行。
const sleep = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, time); }) }; const start = async () => { console.log("start"); // 在這里使用起來就像同步代碼那樣直觀 await sleep(1000); console.log("end"); }; start();
控制臺先輸出 start,稍等 1 秒后,輸出結果 ok,最后輸出 end。
解析一下上述代碼:
async 表示這是一個async函數,await 只能用在這個函數里面。
await 表示在這里等待 promise 返回了結果,再繼續執行。
使用起來,就像寫同步代碼一樣地優雅。
總結JavaScript的異步編寫方式,從 回調函數 到 async/await,感覺在寫法上,每次都有進步,其本質就是一次次對語言層抽象的優化。以至于現在,我們可以像同步一樣地,去處理異步。
換句話說就是:異步編程的最高境界,就是根本不用關心它是不是異步。
PS:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101014.html
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:調用棧被清空,消息隊列中并無任務,線程停止,事件循環結束。不確定的時間點請求返回,將設定好的回調函數放入消息隊列。調用棧執行完畢執行消息隊列任務。請求并發回調函數執行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎中的重點,也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執行的時候是從上到下按順序執行,一段代碼執行了之后才會執行下一段代碼,這種方式...
摘要:下面我將介紹的基本用法以及如何在異步編程中使用它們。在沒有發布之前,作為異步編程主力軍的回調函數一直被人詬病,其原因有太多比如回調地獄代碼執行順序難以追蹤后期因代碼變得十分復雜導致無法維護和更新等,而的出現在很大程度上改變了之前的窘境。 前言 自己著手準備寫這篇文章的初衷是覺得如果想要更深入的理解 JS,異步編程則是必須要跨過的一道坎。由于這里面涉及到的東西很多也很廣,在初學 JS 的...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經典面試題從輸入到頁面加載發生了什么這是一篇開發的科普類文章,涉及到優化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結思考,循序漸進的理解 TypeScript。 網絡基礎知識之 HTTP 協議 詳細介紹 HTT...
閱讀 797·2023-04-25 22:57
閱讀 3051·2021-11-23 10:03
閱讀 613·2021-11-22 15:24
閱讀 3156·2021-11-02 14:47
閱讀 2901·2021-09-10 11:23
閱讀 3115·2021-09-06 15:00
閱讀 3936·2019-08-30 15:56
閱讀 3322·2019-08-30 15:52