欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2017年09月09日 10:37:27   作者:大灰狼的小綿羊哥哥  
本篇文章主要介紹了深入理解ES7的async/await的用法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

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

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

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

async/await是什么

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

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

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

對比Promise,co,async/await

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

我們采用mongodb的nodejs驅(qū)動,查詢mongodb數(shù)據(jù)庫作為例子,原因是mongodb的js驅(qū)動已經(jīng)默認實現(xiàn)了返回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鏈。

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

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

可以說,這個使用Promise鏈寫的代碼,比層層調(diào)用回調(diào)函數(shù)更優(yōu)雅,流程也更明確。先獲得數(shù)據(jù)庫對象,再獲得集合對象,最后查詢數(shù)據(jù)。

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

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

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

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

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嵌套,這樣在最后一個查詢操作里面,就可以調(diào)用外面的db對象了。但是這中方式,并不推薦。原因很簡單,我們從一種回調(diào)函數(shù)地獄走向了另一種Promise回調(diào)地獄。

而且,我們要對每個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及其每次的其他結(jié)果組成一個對象返回。請注意,如果每次的結(jié)果都是一個同步的值還好說,但是如果是一個Promise值,每一個Promise都需要多做一層解析。

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

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

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

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

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

co搭配生成器函數(shù)

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

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是一個函數(shù),將接受一個生成器函數(shù)作為參數(shù),去執(zhí)行這個生成器函數(shù)。生成器函數(shù)中使用 yield 關(guān)鍵字來“同步”獲取每個異步操作的值。

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

使用co模塊,我們要將所有的操作包裝成一個生成器函數(shù),然后使用co()去調(diào)用這個生成器函數(shù)??瓷先ヒ策€可以接受,但是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。同時生成器函數(shù)變成了普通函數(shù)。

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

async函數(shù)返回一個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函數(shù),最后返回得到的博客列表最終會被包裝成一個Promise返回,如上,我們直接調(diào)用getBlogs().then()方法可獲取async函數(shù)返回值。

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

深入async/await

async返回值

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

如果async函數(shù)返回的是一個同步的值,這個值將被包裝成一個理解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函數(shù),返回值被包裝成一個Promise,可以調(diào)用then()方法獲取返回值。

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

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

async函數(shù)的異常

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

我們直接在async函數(shù)中拋出一個異常,由于返回的是一個Promise,因此,這個異??梢哉{(diào)用返回Promise的catch()方法捕捉到。

和Promise鏈的對比:

我們的async函數(shù)中可以包含多個異步操作,其異常和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故意直接拋出異常,從輸出結(jié)果來看,count()拋出異常后,直接由catch()捕捉到了,list()并沒有繼續(xù)執(zhí)行。

并行

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

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

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

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()“同時”執(zhí)行,這里count()和list()可以看作是“并行”執(zhí)行的,所耗時間將是兩個異步操作中耗時最長的耗時。

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

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

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

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

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

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

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

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());
})();

執(zhí)行此段代碼,可以在終端中看到結(jié)果:

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 函數(shù)可以正常的返回結(jié)果和拋出異常。await 函數(shù)調(diào)用即可拿到結(jié)果,在外面包上 try/catch 就可以捕獲異常。下面是一個從豆瓣 API 獲取數(shù)據(jù)的例子:

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 函數(shù)的用法

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

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)有所了解,會更容易理解。本文以體驗為主,不會深入說明,結(jié)尾有詳細的文章引用。

第一個例子

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

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。

基本規(guī)則

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

獲得返回值

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); // 這里得到了一個返回錯誤
  
  // 所以以下代碼不會被執(zhí)行了
  console.log('end');
 } catch (err) {
  console.log(err); // 這里捕捉到錯誤 `error`
 }
};

循環(huán)多個await

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

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

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

..省略以上代碼

let one2ten = [1,2,3,4,5,6,7,8,9,10];

// 錯誤示范
one2ten.forEach(function (v) {
 console.log(`當前是第${v}次等待..`);
 await sleep(1000); // 錯誤!! await只能在async函數(shù)中運行
});

// 正確示范
for(var v of one2ten) {
 console.log(`當前是第${v}次等待..`);
 await sleep(1000); // 正確, for循環(huán)的上下文還在async函數(shù)中
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • js數(shù)組去重的常用方法總結(jié)

    js數(shù)組去重的常用方法總結(jié)

    本篇文章主要是對js數(shù)組去重的三種常用方法進行了總結(jié)介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • JavaScript代碼簡單實現(xiàn)求楊輝三角給定行的最大值

    JavaScript代碼簡單實現(xiàn)求楊輝三角給定行的最大值

    求楊輝三角給定行的最大值的的方法或許有很多,下面使用js代碼簡單實現(xiàn)下,感興趣的朋友可以參考下
    2013-10-10
  • 淺談JavaScript 瀏覽器對象

    淺談JavaScript 瀏覽器對象

    下面小編就為大家?guī)硪黄獪\談JavaScript 瀏覽器對象。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • javascript類數(shù)組的深入理解

    javascript類數(shù)組的深入理解

    這篇文章主要給大家介紹了關(guān)于javascript類數(shù)組的深入理解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • JavaScript實現(xiàn)自動彈出窗口并自動關(guān)閉窗口的方法

    JavaScript實現(xiàn)自動彈出窗口并自動關(guān)閉窗口的方法

    這篇文章主要介紹了JavaScript實現(xiàn)自動彈出窗口并自動關(guān)閉窗口的方法,可實現(xiàn)從頁面左側(cè)彈出窗口5秒后窗口向右移動并消失的效果,涉及javascript針對頁面窗口及樣式的定義操作技巧,需要的朋友可以參考下
    2015-08-08
  • JavaScript實現(xiàn)郵箱地址自動匹配功能代碼

    JavaScript實現(xiàn)郵箱地址自動匹配功能代碼

    這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)E-mail郵箱地址自動匹配功能代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • javascript九宮格圖片隨機打亂位置的實現(xiàn)方法

    javascript九宮格圖片隨機打亂位置的實現(xiàn)方法

    這篇文章主要為大家詳細介紹了javascript九宮格圖片隨機打亂位置的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • JS.findElementById()使用介紹

    JS.findElementById()使用介紹

    JS.findElementById()想必大家并不陌生吧,下面有個不錯的示例,感興趣的朋友可以參考下
    2013-09-09
  • 基于javascript實現(xiàn)右下角浮動廣告效果

    基于javascript實現(xiàn)右下角浮動廣告效果

    這篇文章主要介紹了基于javascript實現(xiàn)右下角浮動廣告效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-01-01
  • checkbox勾選判斷代碼分析

    checkbox勾選判斷代碼分析

    我們在做項目的時候,特別是注冊用戶的時候,基本上都需要有注冊協(xié)議之類的東西,那么我們?nèi)绾闻袛嘤脩羰欠窆催x了同意了此協(xié)議呢?
    2014-06-06

最新評論