摘要:回調函數這是異步編程最基本的方法。對象對象是工作組提出的一種規范,目的是為異步編程提供統一接口。誕生后,出現了函數,它將異步編程帶入了一個全新的階段。
更多詳情點擊http://blog.zhangbing.club/Ja...
Javascript 語言的執行環境是“單線程”的,如果沒有異步編程,根本沒法用,非卡死不可。
為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)兩種模式概念很好理解。
ES6 誕生以前,異步編程的方法,大概有下面四種:回調函數 ,事件監聽 ,發布/訂閱 ,Promise對象。
回調函數這是異步編程最基本的方法。
假定有兩個函數f1和f2,后者等待前者的執行結果。
f1(); f2();
如果f1是一個很耗時的任務,可以考慮改寫f1,把f2寫成f1的回調函數。
function f1(callback){ setTimeout(function () { // f1的任務代碼 callback(); }, 1000); }
執行代碼就變成下面這樣:
f1(f2);
采用這種方式,我們把同步操作變成了異步操作,f1不會堵塞程序運行,相當于先執行程序的主要邏輯,將耗時的操作推遲執行。
回調函數的優點是簡單、容易理解和部署,缺點是不利于代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回調函數。
事件監聽另一種思路是采用事件驅動模式。任務的執行不取決于代碼的順序,而取決于某個事件是否發生。
還是以f1和f2為例。首先,為f1綁定一個事件(這里采用的jQuery的寫法)。
f1.on("done", f2);
上面這行代碼的意思是,當f1發生done事件,就執行f2。然后,對f1進行改寫:
function f1(){ setTimeout(function () { // f1的任務代碼 f1.trigger("done"); }, 1000); }
f1.trigger("done")表示,執行完成后,立即觸發done事件,從而開始執行f2。
這種方法的優點是比較容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以"去耦合"(Decoupling),有利于實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。
發布/訂閱上一節的"事件",完全可以理解成"信號"。
我們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發布"(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);
這種方法的性質與"事件監聽"類似,但是明顯優于后者。因為我們可以通過查看"消息中心",了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。
Promises對象Promises對象是CommonJS工作組提出的一種規范,目的是為異步編程提供統一接口。
簡單說,它的思想是,每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數。比如,f1的回調函數f2,可以寫成:
f1().then(f2);
f1要進行如下改寫(這里使用的是jQuery的實現):
function f1(){ var dfd = $.Deferred(); setTimeout(function () { // f1的任務代碼 dfd.resolve(); }, 500); return dfd.promise; }
這樣寫的優點在于,回調函數變成了鏈式寫法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以實現許多強大的功能。
比如,指定多個回調函數:
f1().then(f2).then(f3);
再比如,指定發生錯誤時的回調函數:
f1().then(f2).fail(f3);
而且,它還有一個前面三種方法都沒有的好處:如果一個任務已經完成,再添加回調函數,該回調函數會立即執行。所以,你不用擔心是否錯過了某個事件或信號。這種方法的缺點就是編寫和理解,都相對比較難。
ES6誕生后,出現了Generator函數,它將 JavaScript 異步編程帶入了一個全新的階段。ES6也將Promise 其寫進了語言標準,統一了用法,原生提供了Promise對象。
故ES6異步編程的方法,大概有兩種:Generator函數,Promise。
Generator函數特點: 帶星號function,yield語句 ,next() 獲取下一個yield表達式中yield后的值,擁有遍歷器接口,與for..of可搭配使用
下面代碼中,Generator函數封裝了一個異步操作,該操作先讀取一個遠程接口,然后從JSON格式的數據解析信息。這段代碼非常像同步操作,除了加上了yield命令
var fetch = require("node-fetch"); function * gen() { var url = "http://api.github.com/users/github"; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); result.value.then(function(data) { return data.json(); }).then(function (data) { g.next(data); });
執行過程:
首先執行Generator函數,獲取遍歷器對象,然后使用next 方法(第二行),執行異步任務的第一階段。由于Fetch模塊返回的是一個Promise對象,因此要用then方法調用下一個next 方法。
缺點:
可以看到,雖然Generator函數將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段),即如何實現自動化的流程管理。
補充拓展
可以參考阮一峰的ECMAScript 6 入門用Thunk函數實現自動化流程管理,對Generator函數進行拓展,前提是每一個異步操作,都要是Thunk函數,進價就是再用CO模塊來實現自動化流程管理,co模塊其實就是將兩種自動執行器(Thunk 函數和 Promise 對象),包裝成一個模塊。使用 co 的前提條件是,Generator 函數的yield命令后面,只能是 Thunk 函數或 Promise 對象。如果數組或對象的成員,全部都是 Promise 對象,也可以使用 co。后面,ES2017標準引入了async函數,對Generator再“語法升級”, async 函數是什么?一句話,它就是 Generator 函數的語法糖。async函數對 Generator 函數進行了改進,體現在以下四點:
內置執行器。
Generator 函數的執行必須靠執行器,所以才有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數一模一樣,只要一行。
更好的語義。
async和await,比起星號和yield,語義更清楚了。async表示函數里有異步操作,await表示緊跟在后面的表達式需要等待結果。
更廣的適用性。
co模塊約定,yield命令后面只能是 Thunk 函數或 Promise 對象,而async函數的await命令后面,可以是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作)。
返回值是 Promise。
async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你可以用then方法指定下一步的操作。進一步說,async函數完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。
PromiseES6 規定,Promise對象是一個構造函數,用來生成Promise實例。
下面代碼創造了一個Promise實例。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } });
Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。
Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。
promise.then(function(value) { // success }, function(error) { // failure });
then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved時調用,第二個回調函數是Promise對象的狀態變為rejected時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作為參數。 Promise 的基本用法就談到這,更深入用法,請參考阮一峰的ECMAScript 6 入門
特別需要指出的是在ES6之前,promise是一套規范和原則,只要設計的庫復合規范的要求就都可以算是promise, 目前比較流行的promise庫(插件)有q和when,RSVP.js,jQuery的Deferred等。ES6后,將Promise 眾多規范中的一種寫入語言標準,ES6中的 Promise 是其中一種,各個 Promise 規范之間有細微的差別(主要是特性上的)
參考來源:
ECMAScript 6 入門
Javascript異步編程的4種方法
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95841.html
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經典面試題從輸入到頁面加載發生了什么這是一篇開發的科普類文章,涉及到優化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結思考,循序漸進的理解 TypeScript。 網絡基礎知識之 HTTP 協議 詳細介紹 HTT...
摘要:異步編程是每個使用編程的人都會遇到的問題,無論是前端的請求,或是的各種異步。本文就來總結一下常見的四種處理異步編程的方法。利用一種鏈式調用的方法來組織異步代碼,可以將原來以回調函數形式調用的代碼改為鏈式調用。 異步編程是每個使用 JavaScript 編程的人都會遇到的問題,無論是前端的 ajax 請求,或是 node 的各種異步 API。本文就來總結一下常見的四種處理異步編程的方法。...
摘要:異步問題回調地獄首先,我們來看下異步編程中最常見的一種問題,便是回調地獄。同時使用也是異步編程最基礎和核心的一種解決思路。基于,目前也被廣泛運用,其是異步編程的一種解決方案,比傳統的回調函數解決方案更合理和強大。 關于 微信公眾號:前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實際編碼中,我們經常會遇到Javascript代碼異步執行的場景...
摘要:一般會這樣去寫要在第一個請求成功后才可以執行下一步這樣的寫法的原理是,當執行一些異步操作時,我們需要知道操作是否已經完成,所有當執行完成的時候會返回一個回調函數,表示操作已經完成。 前言 開篇首先設想一個日常開發常常會遇到的需求:在多個接口異步請求數據,然后利用這些數據來進行一系列的操作。一般會這樣去寫: $.ajax({ url: ......, success: f...
摘要:解決辦法,將箭頭函數聲明為函數,代碼如下運行結果至此,問題解決。必須在函數的上下文中。對程序而言有了上下文調用幀才有一個完整的邏輯過程。 先簡單介紹下async await: async/await是ES6推出的異步處理方案,目的也很明確:更好的實現異步編程。 詳細見阮大神 ES6入門 現在說說實踐中遇到的問題:使用await報錯Unexpected identifier 先...
閱讀 955·2019-08-30 14:24
閱讀 987·2019-08-30 14:13
閱讀 1799·2019-08-29 17:21
閱讀 2660·2019-08-29 13:44
閱讀 1654·2019-08-29 11:04
閱讀 438·2019-08-26 10:44
閱讀 2564·2019-08-23 14:04
閱讀 908·2019-08-23 12:08