<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當前位置: 首頁 - 科技 - 知識百科 - 正文

        深入理解ES7的async/await的用法

        來源:懂視網 責編:小采 時間:2020-11-27 22:30:51
        文檔

        深入理解ES7的async/await的用法

        深入理解ES7的async/await的用法:在最開始學習ES6的Promise時,曾寫過一篇博文 《promise和co搭配生成器函數方式解決js代碼異步流程的比較》 ,文章中對比了使用Promise和co模塊搭配生成器函數解決js異步的異同。 在文章末尾,提到了ES7的async和await,只是當時只是簡單的提了一下,并
        推薦度:
        導讀深入理解ES7的async/await的用法:在最開始學習ES6的Promise時,曾寫過一篇博文 《promise和co搭配生成器函數方式解決js代碼異步流程的比較》 ,文章中對比了使用Promise和co模塊搭配生成器函數解決js異步的異同。 在文章末尾,提到了ES7的async和await,只是當時只是簡單的提了一下,并

        在最開始學習ES6的Promise時,曾寫過一篇博文 《promise和co搭配生成器函數方式解決js代碼異步流程的比較》 ,文章中對比了使用Promise和co模塊搭配生成器函數解決js異步的異同。

        在文章末尾,提到了ES7的async和await,只是當時只是簡單的提了一下,并未做深入探討。

        在前兩個月發布的Nodejs V7中,已添加了對async和await的支持,今天就來對這個東東做一下深入的探究。以更加優雅的方法寫異步代碼。

        async/await是什么

        async/await可以說是co模塊和生成器函數的語法糖。用更加清晰的語義解決js異步代碼。

        熟悉co模塊的同學應該都知道,co模塊是TJ大神寫的一個使用生成器函數來解決異步流程的模塊,可以看做是生成器函數的執行器。而async/await則是對co模塊的升級,內置生成器函數的執行器,不再依賴co模塊。同時,async返回的是Promise。

        從上面來看,不管是co模塊還是async/await,都是將Promise作為最基礎的單元,對Promise不很了解的同學可以先深入了解一下Promise。

        對比Promise,co,async/await

        下面我們使用一個簡單的例子,來對比一下三種方式的異同,以及取舍。

        我們采用mongodb的nodejs驅動,查詢mongodb數據庫作為例子,原因是mongodb的js驅動已經默認實現了返回Promise,而不用我們單獨去包裝Promise了。

        使用Promise鏈

        MongoClient.connect(url + db_name).then(db=> {
         return db.collection('blogs');
        }).then(coll=> {
         return coll.find().toArray();
        }).then(blogs=> {
         console.log(blogs.length);
        }).catch(err=> {
         console.log(err);
        })

        Promise的then()方法可以返回另一個Promise,也可以返回一個同步的值,如果返回的是一個同步值,將會被包裝成一個Promise。

        上面的例子中,db.collection()將返回一個同步的值,即集合對象,但是被包裝成Promise,將會透傳到下一個then()方法。

        上面一個例子,是使用的Promise鏈。

        先連接數據庫MongoClient.connect()返回一個Promise,然后在then()方法里獲得數據庫對象db,然后再獲取到coll對象再返回。在下一個then()方法獲得coll對象,然后進行查詢,查詢結果返回,逐層調用then()方法,形成一個Promise鏈。

        在這個Promise鏈上,如果任何一個環節出現異常,都會被最后的catch()捕捉到。

        可以說,這個使用Promise鏈寫的代碼,比層層調用回調函數更優雅,流程也更明確。先獲得數據庫對象,再獲得集合對象,最后查詢數據。

        但是這里有個不怎么“優雅”的問題,在于,每一個then()方法獲取的對象,都是上一個then()方法返回的數據。而不能跨層訪問。

        什么意思,就是說在第三個then(blogs => {})中我們只能獲取到查詢的結果blogs,而不能使用上面的db對象和coll對象。這個時候,如果要打印出blogs列表后,要關閉數據庫db.close()怎么辦?

        這個時候,可以兩種解決方法:

        第一種是,使用then()嵌套。我們將Promise鏈打斷,使之嵌套,猶如使用回調函數的嵌套一般:

        MongoClient.connect(url + db_name).then(db=> {
         let coll = db.collection('blogs');
         coll.find().toArray().then(blogs=> {
         console.log(blogs.length);
         db.close();
         }).catch(err=> {
         console.log(err);
         });
        }).catch(err=> {
         console.log(err);
        })

        這里我們將兩個Promise嵌套,這樣在最后一個查詢操作里面,就可以調用外面的db對象了。但是這中方式,并不推薦。原因很簡單,我們從一種回調函數地獄走向了另一種Promise回調地獄。

        而且,我們要對每個Promise的異常進行捕捉,因為Promise沒有形成鏈。

        還有一種方式, 是在每個then()方法里都將db傳過來:

        MongoClient.connect(url + db_name).then(db=> {
         return {db:db,coll:db.collection('blogs')};
        }).then(result=> {
         return {db:result.db,blogs:result.coll.find().toArray()};
        }).then(result=> {
         return result.blogs.then(blogs=> { //注意這里,result.coll.find().toArray()返回的是一個Promise,因此這里需要再解析一層
         return {db:result.db,blogs:blogs}
         })
        }).then(result=> {
         console.log(result.blogs.length);
         result.db.close();
        }).catch(err=> {
         console.log(err);
        });
        

        我們在每個then()方法的返回中,都將db及其每次的其他結果組成一個對象返回。請注意,如果每次的結果都是一個同步的值還好說,但是如果是一個Promise值,每一個Promise都需要多做一層解析。

        例如上面的一個例子,第二個then()方法返回的 {db:result.db,blogs:result.coll.find().toArray()} 對象中, blogs 是一個Promise,在下一個then()方法中,我們無法直接引用博客列表數組值,因此需要先調用then()方法解析一層,然后將兩個同步值db和blogs返回。

        注意,這里涉及到了Promise的嵌套,不過一個Promise只嵌套一層then()。

        這種方式,也是很蛋疼的一個方式,因為如果遇到then()方法中返回的不是同步的值,而是Promise的話,我們需要多做很多工作。而且,每次都透傳一個“多余”的db對象,在邏輯上也有點冗余。

        但除此之外,對于Promise鏈的使用,如果遇到上面的問題,好像也沒其他更好的方法解決了。我們只能根據場景去選擇一種“最優”的方案,如果要使用Promise鏈的話。

        鑒于Promise上面蛋疼的問題,TJ大神將ES6中的生成器函數,用co模塊包裝了一下,以更優雅的方式來解決上面的問題。

        co搭配生成器函數

        如果使用co模塊搭配生成器函數,那么上面的例子可以改寫如下:

        const co = require('co');
        co(function* (){
         let db = yield MongoClient.connect(url + db_name);
         let coll = db.collection('blogs');
         let blogs = yield coll.find().toArray();
         console.log(blogs.length);
         db.close();
        }).catch(err=> {
         console.log(err);
        });
        

        co是一個函數,將接受一個生成器函數作為參數,去執行這個生成器函數。生成器函數中使用 yield 關鍵字來“同步”獲取每個異步操作的值。

        上面代碼在代碼形式上,比上面使用Promise鏈要優雅,我們消滅了回調函數,代碼看起來都是同步的。除了使用co和yield有點怪之外。

        使用co模塊,我們要將所有的操作包裝成一個生成器函數,然后使用co()去調用這個生成器函數。看上去也還可以接受,但是ES的進化是不滿足于此的,于是async/await被提到了ES7的提案。

        async/await

        我們先看一下使用async/await改寫上面的代碼:

        (async function(){
         let db = await MongoClient.connect(url + db_name);
         let coll = db.collection('blogs');
         let blogs = await coll.find().toArray();
         console.log(blogs.length);
         db.close();
        })().catch(err=> {
         console.log(err);
        });
        

        我們對比代碼可以看出,async/await和co兩種方式代碼極為相似。

        co換成了async,yield換成了await。同時生成器函數變成了普通函數。

        這種方式在語義上更加清晰明了,async表明這個函數是異步的,同時await表示要“等待”異步操作返回值。

        async函數返回一個Promise,上面的代碼其實是這樣:

        let getBlogs = async function(){
         let db = await MongoClient.connect(url + db_name);
         let coll = db.collection('blogs');
         let blogs = await coll.find().toArray();
         db.close();
         return blogs;
        };
        
        getBlogs().then(result=> {
         console.log(result.length);
        }).catch(err=> {
         console.log(err);
        })
        
        

        我們定義getBlogs為一個async函數,最后返回得到的博客列表最終會被包裝成一個Promise返回,如上,我們直接調用getBlogs().then()方法可獲取async函數返回值。

        好了,上面我們簡單對比了一下三種解決異步方案,下面我們來深入了解一下async/await。

        深入async/await

        async返回值

        async用于定義一個異步函數,該函數返回一個Promise。

        如果async函數返回的是一個同步的值,這個值將被包裝成一個理解resolve的Promise,等同于return Promise.resolve(value)

        await用于一個異步操作之前,表示要“等待”這個異步操作的返回值。await也可以用于一個同步的值。

        //返回一個Promise
        let timer = async functiontimer(){
         return new Promise((resolve,reject) => {
         setTimeout(()=> {
         resolve('500');
         },500);
         });
        }
        
        timer().then(result=> {
         console.log(result); //500
        }).catch(err=> {
         console.log(err.message);
        });
        
        //返回一個同步的值
        let sayHi = async functionsayHi(){
         let hi = await 'hello world'; 
         return hi; //等同于return Promise.resolve(hi);
        }
        
        sayHi().then(result=> {
         console.log(result);
        });
        

        上面這個例子返回是一個同步的值,字符串'hello world',sayHi()是一個async函數,返回值被包裝成一個Promise,可以調用then()方法獲取返回值。

        對于一個同步的值,可以使用await,也可以不使用await。效果效果是一樣的。具體用不用,看情況。

        比如上面使用mongodb查詢博客那個例子, let coll = db.collection('blogs'); ,這里我們就沒有用await,因為這是一個同步的值。當然,也可以使用await,這樣會顯得代碼統一。雖然效果是一樣的。

        async函數的異常

        let sayHi = async functionsayHi(){
         throw new Error('出錯了');
        }
        sayHi().then(result=> {
         console.log(result);
        }).catch(err=> {
         console.log(err.message); //出錯了
        });

        我們直接在async函數中拋出一個異常,由于返回的是一個Promise,因此,這個異常可以調用返回Promise的catch()方法捕捉到。

        和Promise鏈的對比:

        我們的async函數中可以包含多個異步操作,其異常和Promise鏈有相同之處,如果有一個Promise被reject()那么后面的將不會再進行。

        let count = ()=>{
         return new Promise((resolve,reject) => {
         setTimeout(()=>{
         reject('故意拋出錯誤');
         },500);
         });
        }
        
        let list = ()=>{
         return new Promise((resolve,reject)=>{
         setTimeout(()=>{
         resolve([1,2,3]);
         },500);
         });
        }
        
        let getList = async ()=>{
         let c = await count();
         let l = await list();
         return {count:c,list:l};
        }
        console.time('begin');
        getList().then(result=> {
         console.log(result);
        }).catch(err=> {
         console.timeEnd('begin');
         console.log(err);
        });
        //begin: 507.490ms
        //故意拋出錯誤
        
        

        如上面的代碼,定義兩個異步操作,count和list,使用setTimeout延時500毫秒,count故意直接拋出異常,從輸出結果來看,count()拋出異常后,直接由catch()捕捉到了,list()并沒有繼續執行。

        并行

        使用async后,我們上面的例子都是串行的。比如上個list()和count()的例子,我們可以將這個例子用作分頁查詢數據的場景。

        先查詢出數據庫中總共有多少條記錄,然后再根據分頁條件查詢分頁數據,最后返回分頁數據以及分頁信息。

        我們上面的例子count()和list()有個“先后順序”,即我們先查的總數,然后又查的列表。其實,這兩個操作并無先后關聯性,我們可以異步的同時進行查詢,然后等到所有結果都返回時再拼裝數據即可。

        let count = ()=>{
         return new Promise((resolve,reject) => {
         setTimeout(()=>{
         resolve(100);
         },500);
         });
        }
        
        let list = ()=>{
         return new Promise((resolve,reject)=>{
         setTimeout(()=>{
         resolve([1,2,3]);
         },500);
         });
        }
        
        let getList = async ()=>{
         let result = await Promise.all([count(),list()]);
         return result;
        }
        console.time('begin');
        getList().then(result=> {
         console.timeEnd('begin'); //begin: 505.557ms
         console.log(result); //[ 100, [ 1, 2, 3 ] ]
        }).catch(err=> {
         console.timeEnd('begin');
         console.log(err);
        });
        
        

        我們將count()和list()使用Promise.all()“同時”執行,這里count()和list()可以看作是“并行”執行的,所耗時間將是兩個異步操作中耗時最長的耗時。

        最后得到的結果是兩個操作的結果組成的數組。我們只需要按照順序取出數組中的值即可。

        JavaScript 中最蛋疼的事情莫過于回調函數嵌套問題。以往在瀏覽器中,因為與服務器通訊是一種比較昂貴的操作,因此比較復雜的業務邏輯往往都放在服務器端,前端 JavaScript 只需要少數幾次 AJAX 請求就可拿到全部數據。

        但是到了 webapp 風行的時代,前端業務邏輯越來越復雜,往往幾個 AJAX 請求之間互有依賴,有些請求依賴前面請求的數據,有些請求需要并行進行。還有在類似 Node.js 的后端 JavaScript 環境中,因為需要進行大量 IO 操作,問題更加明顯。這個時候使用回調函數來組織代碼往往會導致代碼難以閱讀。

        現在比較流行的解決這個問題的方法是使用 Promise,可以將嵌套的回調函數展平。但是寫代碼和閱讀依然有額外的負擔。

        另外一個方案是使用 ES6 中新增的 generator,因為 generator 的本質是可以將一個函數執行暫停,并保存上下文,再次調用時恢復當時的狀態。co 模塊是個不錯的封裝。但是這樣略微有些濫用 generator 特性的感覺。

        ES7 中有了更加標準的解決方案,新增了 async/await 兩個關鍵詞。async 可以聲明一個異步函數,此函數需要返回一個 Promise 對象。await可以等待一個 Promise 對象 resolve,并拿到結果。

        比如下面的例子,以往我們無法在 JavaScript 中使用常見的 sleep 函數,只能使用 setTimeout 來注冊一個回調函數,在指定的時間之后再執行。有了 async/await 之后,我們就可以這樣實現了:

        async function sleep(timeout) {
         return new Promise((resolve, reject) => {
         setTimeout(function() {
         resolve();
         }, timeout);
         });
        }
        
        (async function() {
         console.log('Do some thing, ' + new Date());
         await sleep(3000);
         console.log('Do other things, ' + new Date());
        })();
        
        

        執行此段代碼,可以在終端中看到結果:

        Do some thing, Mon Feb 23 2015 21:52:11 GMT+0800 (CST)
        Do other things, Mon Feb 23 2015 21:52:14 GMT+0800 (CST)

        另外 async 函數可以正常的返回結果和拋出異常。await 函數調用即可拿到結果,在外面包上 try/catch 就可以捕獲異常。下面是一個從豆瓣 API 獲取數據的例子:

        var fetchDoubanApi = function() {
         return new Promise((resolve, reject) => {
         var xhr = new XMLHttpRequest();
         xhr.onreadystatechange = function() {
         if (xhr.readyState === 4) {
         if (xhr.status >= 200 && xhr.status < 300) {
         var response;
         try {
         response = JSON.parse(xhr.responseText);
         } catch (e) {
         reject(e);
         }
         if (response) {
         resolve(response, xhr.status, xhr);
         }
         } else {
         reject(xhr);
         }
         }
         };
         xhr.open('GET', 'https://api.douban.com/v2/user/aisk', true);
         xhr.setRequestHeader("Content-Type", "text/plain");
         xhr.send(data);
         });
        };
        
        (async function() {
         try {
         let result = await fetchDoubanApi();
         console.log(result);
         } catch (e) {
         console.log(e);
         }
        })();
        
        

        async 函數的用法

        同 Generator 函數一樣,async 函數返回一個 Promise 對象,可以使用 then 方法添加回調函數。當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操作完成,再接著執行函數體內后面的語句。
        下面是一個例子。

        async function getStockPriceByName(name) {
         var symbol = await getStockSymbol(name);
         var stockPrice = await getStockPrice(symbol);
         return stockPrice;
        }
        
        getStockPriceByName('goog').then(function (result){
         console.log(result);
        });
        

        閱讀本文前,期待您對promise和ES6(ECMA2015)有所了解,會更容易理解。本文以體驗為主,不會深入說明,結尾有詳細的文章引用。

        第一個例子

        Async/Await應該是目前最簡單的異步方案了,首先來看個例子。這里我們要實現一個暫停功能,輸入N毫秒,則停頓N毫秒后才繼續往下執行。

        var sleep = function (time) {
         return new Promise(function (resolve, reject) {
         setTimeout(function () {
         resolve();
         }, time);
         })
        };
        
        var start = async function () {
         // 在這里使用起來就像同步代碼那樣直觀
         console.log('start');
         await sleep(3000);
         console.log('end');
        };
        
        start();
        
        

        控制臺先輸出start,稍等3秒后,輸出了end。

        基本規則

        async 表示這是一個async函數,await只能用在這個函數里面。await表示在這里等待promise返回結果了,再繼續執行。await 后面跟著的應該是一個promise對象(當然,其他返回值也沒關系,只是會立即執行,不過那樣就沒有意義了…)

        獲得返回值

        await等待的雖然是promise對象,但不必寫.then(..),直接可以得到返回值。

        var sleep = function (time) {
         return new Promise(function (resolve, reject) {
         setTimeout(function () {
         // 返回 ‘ok'
         resolve('ok');
         }, time);
         })
        };
        
        var start = async function () {
         let result = await sleep(3000);
         console.log(result); // 收到 ‘ok'
        };
        
        

        捕捉錯誤

        既然.then(..)不用寫了,那么.catch(..)也不用寫,可以直接用標準的try catch語法捕捉錯誤。

        var sleep = function (time) {
         return new Promise(function (resolve, reject) {
         setTimeout(function () {
         // 模擬出錯了,返回 ‘error'
         reject('error');
         }, time);
         })
        };
        
        var start = async function () {
         try {
         console.log('start');
         await sleep(3000); // 這里得到了一個返回錯誤
         
         // 所以以下代碼不會被執行了
         console.log('end');
         } catch (err) {
         console.log(err); // 這里捕捉到錯誤 `error`
         }
        };
        
        

        循環多個await

        await看起來就像是同步代碼,所以可以理所當然的寫在for循環里,不必擔心以往需要閉包才能解決的問題。

        ..省略以上代碼
        var start = async function () {
         for (var i = 1; i <= 10; i++) {
         console.log(`當前是第${i}次等待..`);
         await sleep(1000);
         }
        };

        值得注意的是,await必須在async函數的上下文中的。

        ..省略以上代碼
        
        let one2ten = [1,2,3,4,5,6,7,8,9,10];
        
        // 錯誤示范
        one2ten.forEach(function (v) {
         console.log(`當前是第${v}次等待..`);
         await sleep(1000); // 錯誤!! await只能在async函數中運行
        });
        
        // 正確示范
        for(var v of one2ten) {
         console.log(`當前是第${v}次等待..`);
         await sleep(1000); // 正確, for循環的上下文還在async函數中
        }
        
        

        聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        深入理解ES7的async/await的用法

        深入理解ES7的async/await的用法:在最開始學習ES6的Promise時,曾寫過一篇博文 《promise和co搭配生成器函數方式解決js代碼異步流程的比較》 ,文章中對比了使用Promise和co模塊搭配生成器函數解決js異步的異同。 在文章末尾,提到了ES7的async和await,只是當時只是簡單的提了一下,并
        推薦度:
        標簽: 使用 async es7
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 国产成人青青热久免费精品| 9久9久女女免费精品视频在线观看| 性做久久久久免费观看| 亚洲最新中文字幕| 美丽的姑娘免费观看在线播放 | 亚洲综合AV在线在线播放| 一级做a爰片久久毛片免费陪| 日韩特黄特色大片免费视频| 亚洲GV天堂无码男同在线观看| 在线观看亚洲免费| 免费国产黄网站在线观看动图| 亚洲成AV人片在线观看WWW| 国产成人精品免费大全| 久久久久亚洲爆乳少妇无| 国产免费一区二区三区不卡 | 成年女人看片免费视频播放器| 亚洲夂夂婷婷色拍WW47| 国产性生交xxxxx免费| 日韩成人毛片高清视频免费看| 一本色道久久综合亚洲精品| 99精品在线免费观看| 亚洲一区二区三区不卡在线播放| 在线观看免费为成年视频| 日日摸夜夜添夜夜免费视频| 亚洲AV无码专区亚洲AV伊甸园| aⅴ在线免费观看| 国产精品亚洲一区二区三区| 亚洲伊人久久精品影院| 亚洲无砖砖区免费| 亚洲av日韩综合一区久热| 亚洲乱码精品久久久久..| 四虎免费影院ww4164h| 狠狠综合亚洲综合亚洲色| 亚洲精品国产精品乱码在线观看| 五月婷婷在线免费观看| 黄色网址在线免费观看| 亚洲成年轻人电影网站www| 日韩免费一区二区三区| 全黄大全大色全免费大片| 亚洲欧美第一成人网站7777| 国产亚洲精久久久久久无码AV|