js異步解決方案 --- 回調函數 vs promise vs generater/yield vs async/await

javascript -- 深度解析異步解決方案

高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設計理念, js天生為異步而生, 正如布道者樸靈在 node深入淺出--(有興趣的可以讀一下, 很有意思^_^) , 異步很早就存在于操作系統的底層, 意外的是,在絕大多數高級編程語言中,異步并不多見,疑似被屏蔽了一搬. 造成這個現象的原因或許令人驚訝, 程序員不太適合通過異步來實現進行程序設計 ^_^.

異步的理念是很好的, 然而在程序員編程過程中確實會出現一些問題, 并不是這種理念不容以讓人接受, 而是當有大量的異步操作時會讓你的代碼可讀性降低, 其中回調函數異步編程容易產生毀掉陷阱, 即 callback hell--(不要急, 后面會詳細講解)

然而 js 社區從為停止其腳步, 最新的 ES7 所推出的 async/await 終極異步解決方案, 說終極可能有所不嚴禁, 然而它確實已經完全將原來通過模塊侵入式的異步編程解脫出來, 可以讓程序員以接近傳統意義上的函數調用實現異步編程, 這是 js 里程碑式變革中極其重要的一部分.

Javascript異步編程解決方案歷史與方法

ES 6以前:

  • 回調函數
    回調函數是最原始的異步編程方案, 上篇文章已經講述, 這里不再累贅, 這里給出傳送門 回調函數之美 然而如果業務邏輯過多時, 回調函數會產生深層嵌套, 對程序員極不友好,
    如下代碼所示有一個業務邏輯, 需要對a, b, c三個文件一次讀取

        var fs = require('fs');fs.readFile('./a.txt', function(err1, data1) {fs.readFile('./b.txt', function(err2, data2) {fs.writeFile('./ab.txt', data1 + data2, function(err) {console.log('read and write done!');});});});

    三個異步函數嵌套看起來挺簡單的, 這里知識簡單假設, 拋磚引玉, 如果有5個,10個甚至更多的異步函數要順序執行,那要嵌套(大家都不喜歡身材橫著長吧哈哈)說實話相當恐怖,代碼會變得異常難讀,難調試,難維護。這就是所謂的回調地獄或者callback hell。正是為了解決這個問題,才有了后面兩節要講的內容,用promise或generator進行異步流程管理。異步流程管理說白了就是為了解決回調地獄的問題。所以說任何事情都有兩面性,異步編程有它獨特的優勢,卻也同時遇到了同步編程根本不會有的代碼組織難題。

  • 事件監聽(事件發布/訂閱)
    事件監聽模式是一種廣泛應用于異步編程的模式, 是回調函數的事件化,即發布/訂閱模式,

        var util = require('util');var events = require('events');function Stream() {events.EventEmitter.call(this);}util.inherits(Stream, events.EventEmitter)let got = new Stream();got.on("done", function (params) {console.log(params);});got.on("done", function (params) {console.log('QWER');});got.emit("done", 'diyige');console.log('-----------------');var emitter = new events.EventEmitter();emitter.on("done", function (params) {console.log(params);});emitter.on("done", function (params) {console.log('ZXCV');});emitter.emit("done", 'dierge');// diyige// QWER// dierge// ZXCV
  • Promise對象
    Promise 是異步編程的一種解決方案,它是比傳統的解決方案——回調函數和事件——更合理和更強大, 它的目的是替換以前回調函數的比不編程方案, 也是后續介紹的異步解決方案的基礎, 它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象, 現在的 js庫幾乎都支持這種異步方案

    promise對象有以下特點

    • 對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變
    • 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

圖片描述

下面為單個promise對象應用方法
    var promise = new Promise(function(resolve,reject){// ... some codeif(/* 異步操作成功 */){resolve(value);}else{reject(error);}});

通常用promise 的時候我們一般把它相應的業務包裝起來下圖所示模擬了一個讀取文件的異步
promise 函數,

    var readFile =  function (params) {return new Promise(function(resolve, reject){setTimeout(function(){resolve(params);}, 2000);});}readFile('file1').then(function (data) {console.log(data);return readFile('file2')}).then(function (data) {console.log(data);return readFile('file3')}).then(function (data) {console.log(data);return readFile('file4')}).then(function (data) {console.log(data);return readFile('file5')}).then(function (data) {console.log(data);})//file1//file2//file3//file4//file5
  • 流程控制庫
    還有一種需要手工調用采能夠處理后續任務的, 在這里只簡單介紹一種, 我們稱之為尾觸發, 常用的關鍵字為 next , 為什么要講到它是因為它是 node 神級框架 express中采用的模式, 這里可能要涉及一些后端node的內容
    在 node 搭建服務器時需要面向 切面編程 ,這就需要各種各樣的中間件

        var app = connect();// Middlewareapp.use(connect.staticCache());app.use(connect.static(__dirname + '/public'));app.use(connect.cookieParser());app.use(connect.session());app.use(connect.query());app.use(connect.bodyParser());app.use(connect.csrf());app.listen(3001);

    在通過 use() 方法監聽好一系列中間件后, 監聽端口上的請求, 中間件采用的是尾觸發的機制, 下面是個一個簡單的中間件

        function (req, res, next) {// express中間件}

    每個中間件傳遞請求對象, 響應對象, 和尾觸發函數, 通過隊列形成一個處理流, 如下圖
    圖片描述
    中間件機制使得在處理網絡請求時, 可以像面向切面編程一樣進行過濾, 驗證, 日志等功能.

ES 6:

  • Generator函數(協程coroutine)
    Generator 函數有多種理解角度。語法上,Generator 函數是一個狀態機,封裝了多個內部狀態。
    執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,還是一個遍歷器對象生成函數。執行函數后返回的是一個遍歷器對象,可以依次遍歷 Generator 函數內部的每一個狀態。

        function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending';}var hw = helloWorldGenerator();hw.next()// { value: 'hello', done: false }hw.next()// { value: 'world', done: false }hw.next()// { value: 'ending', done: true }hw.next()// { value: undefined, done: true }

    下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)為止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法可以恢復執行。

  • 基于 Promise 對象的自動執行
    generater/yield函數還無法真正解決異步方案的問題, 需要配合額外的執行模塊 如 TJ Holowaychuk 的 co 模塊, 在這里用promise模塊進行generater函數的自動執行;

        var fs = require('fs');var readFile = function (fileName){return new Promise(function (resolve, reject){fs.readFile(fileName, function(error, data){if (error) return reject(error);resolve(data);});});};var gen = function* (){var f1 = yield readFile('/etc/fstab');var f2 = yield readFile('/etc/shells');console.log(f1.toString());console.log(f2.toString());};
    /*****************************************var g = gen();g.next().value.then(function(data){g.next(data).value.then(function(data){g.next(data);});});
    *****************************************/// 自動執行函數        function run(gen){var g = gen();function next(data){var result = g.next(data);if (result.done) return result.value;result.value.then(function(data){next(data);});}next();}run(gen);   

ES 7:

  • async/await
    終于來到了我們夢寐以求的的"終極"異步解決方案, 或許你有些失望, 當然這種失望是async/await 僅僅是語法糖, async/await 就是 generater/yield/promise + 自動執行模塊的封裝.相對于前輩 async 函數可以自動執行 并且 await 關鍵字后面則只能帶promise隊形--這里注意 await 后面支持其他數據類型, 但是底層也會將其轉化為promise對象

    async函數對 Generator 函數的改進,體現在以下四點。

    • 內置執行器。
      Generator 函數的執行必須靠執行器,所以才有了co模塊,而async函數自帶執行器,這完全不像 Generator 函數,需要調用next方法,或者用co模塊,才能真正執行,得到最后結果。
    • 更好的語義。
      async和await,比起星號和yield,語義更清楚了。async表示函數里有異步操作,await表示緊跟在后面的表達式需要等待結果。
    • 更廣的適用性。
      co模塊約定,yield命令后面只能是 Thunk 函數或 Promise 對象,而async函數的await命令后面,可以是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作)
    • 返回值是 Promise。
      async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你可以用then方法指定下一步的操作。進一步說,async函數完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

          function name(params) {return new Promise(function (resolve, reject) {setTimeout(() => {resolve(params)}, 3000);});}async function myf () {let gf = await name('xiaohua');let gf2 = await name('xiaohong');return gf + gf2 }async function myf3 (params) {let aaa = await myf();return aaa;}myf3().then(function (params) {console.log(params);});// xiaohuaxiaohong
async/await 對前者的generater/yield 進行了高度的封裝配合那些支持 promise 實現的庫可以完美的像普通函數一樣調用, 并且async函數與其他async函數也可以完美無縫連接, 堪稱終極方案

koa2已經支持 async/await 但是最新的 express框架依然沒有支持這種寫法, async/await 是大勢所趨, 或許不久的將來 express也會支持它, 我們拭目以待

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/452766.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/452766.shtml
英文地址,請注明出處:http://en.pswp.cn/news/452766.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是TPDU

TPDU,全稱Transport Protocol Data Unit,是指傳送協議數據單元。代表從一個傳輸實體發送至另一個傳輸實體的消息。 我們需要為傳輸實體之間交換的數據單元起一個更加一般化的名字,TCP的術語是數據段,它很容易混淆,而且在TCP領域之…

sql注入基本原理

1. 參考文獻: 趣解SQL注入原理 Sql注入基本原理 2.參考書籍

項目管理雜談-員工的積極性在哪里?

項目開發過程中,每每有人感嘆,曾幾何時,隊伍如何好帶,如何好用,而如今,人心繁雜,隊伍不好帶了。很多人的想法是“人望高處走”,不停的尋找待遇及其他方面更好的單位。其實&#xff0…

centos7硬盤分區

首先在虛擬機的設置中為系統添加硬盤 使用fdisk -l /dev/sdb 查看未分區的硬盤 fdisk -l /dev/sda 這是已經分區好得 接下來我們就要對sdb進行分區: 首先使用fdisk /dev/sdb 接著輸入m可以看到詳細命令 進行添加分區 已經建立好4個主分區,在建立時會看到以下 刪除…

java上傳rar文件_java實現上傳zip/rar壓縮文件,自動解壓

在pom中添加解壓jar依賴4.0.0org.springframework.bootspring-boot-starter-parent2.1.2.RELEASEcom.hfuncompress0.0.1-SNAPSHOTuncompress上傳壓縮文件(rar或者zip格式),解壓1.8org.springframework.bootspring-boot-starter-weborg.projectlomboklomboktrueorg.springframew…

從MapReduce的執行來看如何優化MaxCompute(原ODPS) SQL

摘要: SQL基礎有這些操作(按照執行順序來排列): from join(left join, right join, inner join, outer join ,semi join) where group by select sum distinct count order by 如果我們能理解mapreduce是怎么實現這些SQL中的基本操…

套接字(socket)基本知識與工作原理

套接字(socket)基本知識與工作原理 一、Socket相關概念 Socket通常也稱作“套接字”,用于描述IP地址和端口,是一個通信鏈的句柄。(其實就是兩個程序通信用的。) SOCKET用于在兩個基于TCP/IP協議的應用程序之…

python 多線程--重點知識

1.全局變量global的用法 2.多線程共享全局變量-args參數 注意args參數類型為元組,逗號不能少!

Flask WTForm表單的使用

運行環境: python2.7 flask 0.11 flask-wtf 0.14.2 wtform能夠通過一個類定義一些字段,這些字段會在前端生成標簽,并且通過設置字段的驗證規則,自動判斷前端輸入數據的格式。 一般用于用戶登錄,用戶注冊等信息錄入。…

Java與C#個人之比較

網上這方面的比較文章已經有不少了,不過大都是要么從很高的角度說的,要么就是從底層說的,本人就以自己這幾年的編程經歷中的感受,來談談自己的體會。 相似性: Java和C#都是一門面向對象的語言,Java更多地…

java利用子類求正方形_Java程序設計實驗2011

(2)掌握對象的聲明和使用;(3)掌握構造方法的概念和使用;(4)掌握類及成員的訪問控制符。2、實驗任務(1)閱讀下面的程序,在main()方法里添加語句完成如下的功能:①創建一個MyV alue類的對象myV alue。②為myV alue對象中的value域賦…

當導用模塊與包的import與from的問題(模塊與包的調用)

當在views.py里寫impor models會不會報錯呢? 1、Python里面的py文件都是每一行的代碼。2、Python解釋器去找一個模塊的時候,只去sys.path的路徑里找3、django項目啟動(django項目的啟動文件是manage.py)啟動項目是將manage.py的路…

ack和seq

ACK (Acknowledgement),即確認字符,在數據通信中,接收站發給發送站的一種傳輸類控制字符。表示發來的數據已確認接收無誤。 seq是序列號,這是為了連接以后傳送數據用的,ack是對收到的數據包的確認&#xff…

MySQL中的information_schema

0.引言 近日在學習網絡安全的sql注入時,用到mysql中的information_schema數據庫,其思路是利用information_schema中的SCHEMA獲取數據庫中的table名稱。現在對相關數據庫進行總結,方便以后復習使用。 2.information_schema數據庫 informati…

linux配置防火墻,開啟端口

linux配置防火墻,開啟端口 Centos7,配置防火墻,開啟端口  1.查看已開放的端口(默認不開放任何端口)    firewall-cmd --list-ports  2.開啟80端口    firewall-cmd --zonepublic(作用域) --add-port80/tcp(端口和訪問類型) --permanent(永久…

使用Intel編譯器系列合集

好的帖子:http://topic.csdn.net/u/20080327/16/071b45df-3795-4bf1-9c4d-da4eb5aaa739.html參考手冊:http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/index.htm 說明:本系列文章為個…

【前端】這可能是你看過最全的css居中解決方案了~

1.水平居中&#xff1a;行內元素解決方案 適用元素&#xff1a;文字&#xff0c;鏈接&#xff0c;及其其它inline或者inline-*類型元素&#xff08;inline-block&#xff0c;inline-table&#xff0c;inline-flex&#xff09; html部分代碼:<div>文字元素</div><…

java手機一款三國游戲_JAVA熱游—富甲三國之雄霸天下原創心得

因為工作忙碌的關系&#xff0c;很長時間都沒有來關注手機游戲論壇&#xff0c;這款富甲三國.雄霸天下&#xff0c;我也是前天才拿到手。游戲比想象中的簡單&#xff0c;個人僅用了兩個小時時間&#xff0c;就將三個人物全部通關。游戲的開始畫面制作得比較精美&#xff0c;而且…

Python多線程--互斥鎖、死鎖

1、互斥鎖 為解決資源搶奪問題&#xff0c;使用mutex Threading.Lock()創建鎖&#xff0c;使用mutex.acquire()鎖定&#xff0c;使用mutex.release()釋放鎖。 代碼一&#xff1a; import threading import time# 定義一個全局變量 g_num 0def test1(num):global g_num# 上鎖…