javascript的非同步操作方法有:1、回呼函數;2、事件監聽;3、「發布/訂閱」模式;4、promise;5、generator;6、「async/await」。
本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。
什麼是非同步操作?
非同步模式並不難理解,例如任務A、B、C,執行A之後執行B,但是B是一個耗時的工作,所以,把B放在任務佇列中,去執行C,然後B的一些I/O等回傳結果之後,再去執行B,這就是非同步操作。
JavaScript為什麼需要非同步操作?
JavaScript語言的執行環境是“單線程”, 所謂單線程,就是一次只能完成一件任務, 如果有多個任務就需要排隊,一個完成了,繼續下一個,這種方式在實作來說是非常簡單的,但是如果一個任務耗時很長,那麼後面的任務就需要排隊等著,會拖延整個程式的執行。 常見的瀏覽器無回應(假死)就是因為某一段JavaScript程式碼長時間運作(例如死迴圈),導致整個頁面卡死,其他任務無法執行。
為了解決這個問題,JavaScript語言將任務的執行模式分為兩種:同步(Synchronous)和非同步(Asynchronous)。
同步任務執行的順序和排隊的順序是一致的,而非同步則需要有一個或多個回呼函數,前一個任務結束後,不是執行後一個任務,而是執行回呼函數,後一個任務則是等著前一個任務結束就執行,所以程式的執行順序與任務的排列順序是不一致的,異步的。
非同步模式非常重要,在瀏覽器端,耗時很長的操作都應該非同步執行,避免瀏覽器失去回應,最好的例子就是ajax操作,在伺服器端, 非同步操作甚至是唯一方式,因為執行環境是單線程的,如果允許同步執行所有的http請求,伺服器效能會急劇下降,很快就會失去回應。
JavaScript中非同步操作的幾個類型。
JavaScript中非同步程式設計的方法有:
- 回呼函數
- 事件監聽
- #發布/訂閱
- #promise
- generator(ES6)
- async/await (ES7)
下面我來分別介紹這幾種非同步方法:
一、回呼函數
回呼函數是非同步程式設計中最基本的方法。假設有三個函數f1、f2、f3,f2需要等待f1的執行結果,而f3是獨立的,不需要f1和f2的結果 ,如果我們寫成同步,就是這樣的:
f1(); f2(); f3();
如果f1執行的很快,可以; 但是如果f1執行的很慢,那麼f2和f3就會被阻塞,無法執行。這樣的效率是非常低的。但是我們可以改寫,將f2寫成是f1的回呼函數,如下:
function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); }
那麼這時候執行程式碼就是這樣:
f1(f2); f3();
這樣,就是一個異步的執行了,即使f1很花時間,但是由於是異步的,那麼f3()就會很快的得到執行,而不會受到f1和f2的影響。
注意: 如果我們把f1寫成這樣呢?
function f1(callback){ // f1的任务代码 callback(); }
然後,我們同樣可以這麼呼叫:
f1(f2); f3()
這時候還是異步的嗎? 答案:不是非同步。 這裡的回呼函數並非真正的回呼函數,如果沒有利用setTimeout含函數,那麼f3()的執行同樣需要等到f1(f2)完全執行完畢,這裡要注意。而我們就是利用setTImeout才能做出真正的回呼函數。
二、事件監聽
另一種非同步的想法是採用事件驅動模式。任務的執行不取決於程式碼的順序, 而取決於某個事件是否發生。還是以f1、f2、f3為例子。首先,為f1綁定一個事件(這裡採用jquery的寫法):
f1.on('done', f2); f3()
這裡的意思是: 當f1發生了done事件,就執行f2, 然後,我們對f1進行改寫:
function f1(){ setTimeout(function () { // f1的任务代码 f1.trigger('done'); }, 1000); }
f1.trigger('done')表示, 執行完成後,立即觸發done事件,從而開始執行f2。
這種方法的優點就是比較容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以去耦合,有利於實現模組化,缺點就是整個程式都要變成事件驅動型,運作流程會變得很不清晰。
三、发布/订阅
第二种方法的事件,实际上我们完全可以理解为“信号”,即f1完成之后,触发了一个 'done',信号,然后再开始执行f2。
我们假定,存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”这个信号, 从而知道什么时候自己可以开始执行。 这个就叫做“发布/订阅模式”, 又称为“观察者”模式 。
这个模式有多种实现, 下面采用Ben Alman的Tiny PUb/Sub,这是jQuery的一个插件。
首先,f2向"信号中心"jquery订阅"done"信号,
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){ setTimeout(function () { // f1的任务代码 jQuery.publish("done"); }, 1000); }
jquery.pushlish("done")的意思是: f1执行完成后,向“信号中心”jQuery发布“done”信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质和“事件监听”非常类似,但是明显是优于前者的,因为我们可以通过查看“消息中心”,了解到存在多少信号、每个信号有多少个订阅者,从而监控程序的运行。
四、promise对象
promise是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);
而且,他还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。 所以,你不用担心是否错过了某个事件或者信号,这种方法的确定就是编写和理解,都比较困难。
五、generator函数的异步应用
在ES6诞生之前,异步编程的方法,大致有下面四种:
- 回调函数
- 事件监听
- 发布/订阅
- promise对象
没错,这就是上面讲得几种异步方法。 而generator函数将JavaScript异步编程带入了一个全新的阶段!
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
协程
传统的编程语言中,早就有了异步编程的解决方案,其中一种叫做协程,意思是多个线程互相协作,完成异步任务。
协程优点像函数,又有点像线程,运行流程如下:
- 第一步,协程
A
开始执行。 - 第二步,协程
A
执行到一半,进入暂停,执行权转移到协程B
。 - 第三步,(一段时间后)协程
B
交还执行权。 - 第四步,协程
A
恢复执行。
上面的协程A,就是异步任务,因为它分为两段(或者多段)执行。
举例来说,读取文件的协程写法如下:
function *asyncJob() { // ...其他代码 var f = yield readFile(fileA); // ...其他代码 }
上面代码的函数asyncJob是一个协程,奥妙就在于yield命令, 它表示执行到此处,执行权交给其他协程,也就是说yield命令是异步两个阶段的分界线。
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续向后执行,它的最大优点就是代码的写法非常像同步操作,如果去除yield命令,简直是一模一样。
协程的Generator函数实现
Generator函数是协程在ES6中的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
整个Generator函数就是一个封装的异步任务,或者说异步任务的容器。 异步任务需要暂停的地方,都用yield语句注明。 如下:
function* gen(x) { var y = yield x + 2; return y; } var g = gen(1); g.next() // { value: 3, done: false } g.next() // { value: undefined, done: true }
在调用gen函数时 gen(1), 会返回一个内部指针(即遍历器)g。 这是Generator函数不同于普通函数的另一个地方,即执行它(调用函数)不会返回结果, 返回的一个指针对象 。调用指针g的next方法,会移动内部指针(即执行异步任务的第一阶段),指向第一个遇到的yield语句,这里我们是x + 2,但是实际上这里只是举例,实际上 x + 2 这句应该是一个异步操作,比如ajax请求。 换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。 value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段。
Generator函数的数据交换和错误处理
Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
next
返回值的value属性,是 Generator 函数向外输出数据;next
方法还可以接受参数,向 Generator 函数体内输入数据。
function* gen(x){ var y = yield x + 2; return y; } var g = gen(1); g.next() // { value: 3, done: false } g.next(2) // { value: 2, done: true }
上面代码中,第一next
方法的value
属性,返回表达式x + 2
的值3
。第二个next
方法带有参数2
,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量y
接收。因此,这一步的value
属性,返回的就是2
(变量y
的值)。
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('出错了'); // 出错了
上面代码的最后一行,Generator 函数体外,使用指针对象的throw
方法抛出的错误,可以被函数体内的try...catch
代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
异步任务的封装
下面看看如何使用 Generator 函数,执行一个真实的异步任务。
var fetch = require('node-fetch'); function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); }
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield
命令。
执行这段代码的方法如下。
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 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
如下:
function* gen(x) { yield 1; yield 2; yield 3; return 4; } var a = gen(); console.log(a.next()); console.log(a.next()); console.log(a.next()); console.log(a.next());
最终,打印台输出
即开始调用gen(),并没有真正的调用,而是返回了一个生成器对象,a.next()的时候,执行第一个yield,并立刻暂停执行,交出了控制权; 接着,我们就可以去a.next() 开始恢复执行。。。 如此循环往复。
每当调用生成器对象的next的方法时,就会运行到下一个yield表达式。 之所以称这里的gen()为生成器函数,是因为区别如下:
- 普通函数使用function来声明,而生成器函数使用 function * 来声明。
- 普通函数使用return来返回值,而生成器函数使用yield来返回值。
- 普通函数式run to completion模式 ,即一直运行到末尾; 而生成器函数式 run-pause-run 模式, 函数可以在执行过程中暂停一次或者多次。并且暂停期间允许其他代码执行。
async/await
async函数基于Generator又做了几点改进:
- 内置执行器,将Generator函数和自动执行器进一步包装。
- 语义更清楚,async表示函数中有异步操作,await表示等待着紧跟在后边的表达式的结果。
- 适用性更广泛,await后面可以跟promise对象和原始类型的值(Generator中不支持)
很多人都认为这是异步编程的终极解决方案,由此评价就可知道该方法有多优秀了。它基于Promise使用async/await来优化then链的调用,其实也是Generator函数的语法糖。 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。
await得到的就是返回值,其内部已经执行promise中resolve方法,然后将结果返回。使用async/await的方式写回调任务:
async function dolt(){ console.time('dolt'); const time1=300; const time2=await step1(time1); const time3=await step2(time2); const result=await step3(time3); console.log(`result is ${result}`); console.timeEnd('dolt'); } dolt();
可以看到,在使用await关键字所在的函数一定要是async关键字修饰的。
功能还很新,属于ES7的语法,但使用Babel插件可以很好的转义。另外await只能用在async函数中,否则会报错。
【相关推荐:javascript学习教程】
以上是javascript有哪些非同步操作方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

React是構建交互式前端體驗的首選工具。 1)React通過組件化和虛擬DOM簡化UI開發。 2)組件分為函數組件和類組件,函數組件更簡潔,類組件提供更多生命週期方法。 3)React的工作原理依賴虛擬DOM和調和算法,提高性能。 4)狀態管理使用useState或this.state,生命週期方法如componentDidMount用於特定邏輯。 5)基本用法包括創建組件和管理狀態,高級用法涉及自定義鉤子和性能優化。 6)常見錯誤包括狀態更新不當和性能問題,調試技巧包括使用ReactDevTools和優

React是一個用於構建用戶界面的JavaScript庫,其核心是組件化和狀態管理。 1)通過組件化和狀態管理簡化UI開發。 2)工作原理包括調和和渲染,優化可通過React.memo和useMemo實現。 3)基本用法是創建並渲染組件,高級用法包括使用Hooks和ContextAPI。 4)常見錯誤如狀態更新不當,可使用ReactDevTools調試。 5)性能優化包括使用React.memo、虛擬化列表和CodeSplitting,保持代碼可讀性和可維護性是最佳實踐。

React通過JSX與HTML結合,提升用戶體驗。 1)JSX嵌入HTML,使開發更直觀。 2)虛擬DOM機制優化性能,減少DOM操作。 3)組件化管理UI,提高可維護性。 4)狀態管理和事件處理增強交互性。

React組件可以通過函數或類定義,封裝UI邏輯並通過props接受輸入數據。 1)定義組件:使用函數或類,返回React元素。 2)渲染組件:React調用render方法或執行函數組件。 3)復用組件:通過props傳遞數據,構建複雜UI。組件的生命週期方法允許在不同階段執行邏輯,提升開發效率和代碼可維護性。

React嚴格模式是一種開發工具,可通過激活其他檢查和警告來突出反應應用中的潛在問題。它有助於識別遺產代碼,不安全的生命週期和副作用,鼓勵現代反應實踐。

本文討論了React的對帳過程,詳細介紹了它如何有效地更新DOM。關鍵步驟包括觸發對帳,創建虛擬DOM,使用擴散算法以及應用最小的DOM更新。它還覆蓋了經家


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

WebStorm Mac版
好用的JavaScript開發工具