return timeout(2000);node
}).then(function(){jquery
console.log("fourth");es6
return timeout(2000);ajax
});正則表達式
因爲須要屢次建立Promise對象,因此用了 timeout函數將它封裝起來,每次調用它都會返回一個 新的Promise對象。當then方法調用後,其內部的回調函數默認會將當前的Promise對象返回。當 然也能夠手動返回一個新的Promise對象。咱們這裏就手動返回了一個新的計時對象,由於須要 從新開始計時。後面繼續用the n方法來觸發異步完成的回調函數。這樣就能夠作到同步的效果, 從而避免了過多的回調嵌套帶來的"回調地獄"問題。express
實際上Promise的應用仍是比較多,好比前面講到的fetch,它就利用了 Promise來實現AJAX的異 步操做:編程
let pm = fetch("/users"); // 獲取Promise對象json
pm.then((response) => response.text()).then(text => {數組
test.innerText = text; //將獲取到的文本寫入到頁面上服務器
})
.catch(e rror => console.log("出錯了 "));
注意:response.text0返回的不是文本,而是Promise對象。因此後面又跟了一個then,然 後重新的Promise對象中獲取文本內容。
Promise做爲ES6提供的一種新的異步編程解決方案,可是它也有問題。好比,代碼並無由於 新方法的出現而減小,反而變得更加複雜,同時理解難度也加大。所以它並非異步實現的最終 形態,後續咱們還會繼續介紹其餘的異步實現方法。
16-3迭代器與生成器
上一節中咱們學習瞭如何使用Promise來實現異步操做。可是它也會存在一些問題,好比代碼量 增多,不易理解。那麼這一節我們將一塊兒來探索其餘解決異步的方法。生成器做爲ES6新增長的 語法,它也可以處理異步的操做。不過再講生成器以前,我們還得理解另一個東西:迭代器。
16-3-1 迭代器(Iterator)
迭代器是一種接口,也能夠說是一種規範。它提供了一種統一的遍歷數據的方法。咱們都知道數 組、集合、對象都有本身的循環遍歷方法。好比數組的循環:
let ary = [1,2,3,4,5,6,7,8,9,10];
//for循環
for(let i = 0;i < ary.length;i++){
console.log(ary[i]);
}
//forEach 循環
ary.forEach(function(ele){
console.log(ele);
});
//for-in循環
for(let i in ary){
console.log(ary[i]);
}
//for-of 循環
for(let ele of ary){
console.log(ele);
}
集合的循環:
let list = new Set([1,2,3,4,5,6,7,8,9,10]);
for(let ele of list){
console.log(ele);
}
對象的循環:
let obj = {
name : 'tom',
age : 25,
gender :'男',
intro : function(){
console.log('my name is '+this.name);
}
}
for(let attr in obj){
console.log(attr);
}
從以上的代碼能夠看到,數組能夠用for、forEach、for-in以及for-of來遍歷。集合能用for-of。對 象能用for-in。也就是說,以上數據類型的遍歷方式都各有不一樣,那麼有沒有統一的方式遍歷這 些數據呢?這就是迭代器存在的意義。它能夠提供統一的遍歷數據的方式,只要在想要遍歷的數 據結構中添加一個支持迭代器的屬性便可。這個屬性寫法是這樣的:
const obj = {
[Symbol.iterator]:function(){}
}
[Symbol.ite rato r]屬性名是固定的寫法,只要是擁有該屬性的對象,就可以用迭代器的方式 進行遍歷。
迭代器的遍歷方法是首先得到一個迭代器的指針,初始時該指針指向第一條數據以前。接着經過 調用n ext方法,改變指針的指向,讓其指向下一條數據。每一次的n ext都會返回一個對象,該對 象有兩個屬性。其中value表明想要獲取的數據,done是個布爾值,false表示當前指針指向的數 據有值。true表示遍歷已經結束。
let ary = [1,2,3];
let it = ary[Symbol.iterator](); // 獲取數組中的迭代器
數組是支持迭代器遍歷的,因此能夠直接獲取其中的迭代器。集合也是同樣。
let list = new Set([1,2,3]);
let it = list.entries(); //獲取set集合中的迭代器
console.log(it.next()); // { value: console.log(it.next()); // { value: console.log(it.next()); // { value: console.log(it.next()); // { value:
set集合中每次遍歷出來的值是一個數組,裏面的第一和第二個元素都是同樣的。
因爲數組和集合都支持迭代器,因此它們均可以用同一種方式來遍歷。es6中提供了一種新的循 環方法叫作f or-of。它實際上就是使用迭代器來進行遍歷,換句話說只有支持了迭代器的數據結 構才能使用for-o f循環。在JS中,默認支持迭代器的結構有:
•函數的arguments對象
這裏面並無包含自定義的對象,因此當咱們建立一個自定義對象後,是沒法經過for-of來循環 遍歷它。除非將iterator接口加入到該對象中:
let obj = { name: 'xiejie', age: 18,
gende r:'男', intro: function () { console.log('my name is ' + this.name);
},
[Symbol.iterator]: function () {
let i = 0;
let keys = Object.keys(this); //獲取當前對象的全部屬性並造成一個數組 return {
next: function () {
return {
value: keys[i++], //外部每次執行next都能獲得數組中的第i個元素 done: i > keys.length //若是數組的數據已經遍歷完則返回true }
}
}
}
}
for ( let attr of obj) { console.log(attr);
// name
// age
// gender
// intro
}
let it = obj[Symbol.iterator]();
經過自定義迭代器就能讓自定義對象使用for-of循環。迭代器的概念及使用方法咱們清楚了, 接下來就是生成器。
16-3-2 生成器(Generator)(擴展)
生成器也是ES6新增長的一種特性。它的寫法和函數很是類似,只是在聲明時多了一個*號。
function* say(){}
const say = function*(){}
|注意:這個*只能寫在function關鍵字的後面。
生成器函數和普通函數並不僅是一個*號的區別。普通函數在調用後,必然開始執行該函數,直 到函數執行完或遇到return爲止。中途是不可能暫停的。可是生成器函數則不同,它能夠經過 yield關鍵字將函數的執行掛起,或者理解成暫停。它的外部在經過調用n ext方法,讓函數繼續執 行,直到遇到下一個yield,或函數執行完畢。
function* say(){ yield "開始";
yield "執行中"; yield "結束";
}
let it = say(); //調用say方法,獲得一個迭代器
調用say函數,這句和普通函數的調用沒什麼區別。可是此時say函數並無執行,而是返回了一 個該生成器的迭代器對象。接下來就和以前同樣,執行next方法,say函數執行,當遇到yield
時,函數被掛起,並返回一個對象。對象中包含value屬性,它的值是yield後面跟着的數據。並 且done的值爲false。再次執行next,函數又被激活,並繼續往下執行,直到遇到下一個yield。
當全部的yield都執行完了,再次調用next時獲得的value就是undefined, done的值爲true。
若是你能理解剛纔講的迭代器,那麼此時的生成器也就很好理解了。它的y ield,其實就是n ext方 法執行後掛起的地方,並獲得你返回的數據。那麼這個生成器有什麼用呢?它的y ield關鍵字能夠 將執行的代碼掛起,外部經過next方法讓它繼續運行。
這和異步操做的原理很是相似,把一個操做分爲兩部分,先執行一部分,而後再執行另一部 分。因此生成器能夠處理和異步相關的操做。咱們知道,異步操做主要是依靠回調函數實現。但 是純回調函數的方式去處理同步效果會帶來「回調地域「的問題。Promise能夠解決這個問題。但 是Promise寫起來代碼比較複雜,不易理解。而生成器又提供了一種解決方案。看下面這個例 子:
function* delay() {
yield new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 2000) })
console.log("go on");
}
let it = delay(); //獲得一個迭代器
// it.next()會執行到第一個 yield 獲得的值爲{ value: Promise { vpending> }, done: false }
// it.next().value 將會獲得_個 Promise
// Promise會在2秒之後調用then方法
// 2秒後調用then方法執行迭代器的下一步
it.next().value.then(() => {
it.next();
});
這個例子實現了等待2秒鐘後,打印字符串"go on"。下面咱們來分析下這段代碼。在delay這個 生成器中,yield後面跟了一個Promise對象。這樣,當外部調用next時就能獲得這個Promise對 象。而後調用它的then函數,等待2秒鐘後Promise中會調用resolve方法,接着the n中的回調函 數被調用。也就是說,此時指定的等待時間已到。而後在the n的回調函數中繼續調用生成器的 next方法,那麼生成器中的代碼就會繼續往下執行,最後輸出字符串"go on"。
例子中時間函數外面爲何要包裹一個Promise對象呢?這是由於時間函數自己就是一個異步方 法,給它包裹一個Promise對象後,外部就能夠經過the n方法來處理異步操做完成後的動做。這 樣,在生成器中,就能夠像寫同步代碼同樣來實現異步操做。好比,利用fetch來獲取遠程服務器 的數據(爲了測試方便,我將用M ockJS來攔截請求)。
<body>
<script src="./jquery-1.12.4.min.js"></script>
<script src="./mock-min.js"></script>
<script>
//攔截Ajax請求
Mock.mock(/\.json/, {
'stuents|5-10': [{
'id|+1': 1,
'name': '@cname',
'gende r': /[男女]/, //在正則表達式匹配的範圍內隨機
'age|15-30': 1, //年齡在15-30之間生成,值1只是用來肯定數據類型 'phone': /1\d{10}/,
'add r': '@county(t rue)', //隨機生成中國的一個省、市、縣數據
'date': "@date('yyyy-MM-dd')"
}]
});
function* getUsers() {
let data = yield new Promise((resolve, reject) => {
$.ajax({
type: "get",
url: "/users.json",
success: function (data) {
resolve(data)
}
});
});
console.log("獲得的 data 爲:",data);
}
let it = getUse rs(); // 返回一個迭代器
// it.next().value會獲得一個Promise, Promise裏面向服務器發送請求獲取數據
//數據獲取成功之後調用then方法,並將獲取到的數據傳遞給then方法
// then方法裏面再次開啓迭代器,執行第二句代碼,並將數據傳遞過去 //在getUse rs函數裏面data變量接收了傳遞過來的數據,並打印出來 it.next().value.then((data) => {
it.next(data);
});
</script>
</body>
在Promise中調用JQuery的AJAX方法,當數據返回後調用resolve,觸發外部then方法的回調函 數,將數據返回給外部。外部的the n方法接收到data數據後,再次調用n ext,移動生成器的指 針,並將data數據傳遞給生成器。因此,在生成器中你能夠看到,我聲明瞭一個data變量來接收 異步操做返回的數據,這裏的代碼就像同步操做同樣,但實際上它是個異步操做。當異步的數據 返回後,纔會執行後面的打印操做。這裏的關鍵代碼就是y ield後面必定是一個Promise對象,因 爲只有這樣外部才能調用the n方法來等待異步處理的結果,而後再繼續作接下來的操做。
以前咱們還講過一個替代AJAX的方法fetch,它自己就是用Promise的方法來實現異步,因此代 碼寫起來會更簡單:
function* getUsers(){
let response = yield fetch("/users");
let data = yield response.json();
console.log("data",data);
}
let it = getUsers();
it.next().value.then((response) => {
it.next(response).value.then((data) => {
it.next(data);
});
});
|因爲mock沒法攔截fetch請求,因此我用nodejs+express搭建了一個mock-server服務器。
這裏的生成器我用了兩次yield,這是由於f etch是一個異步操做,得到了響應信息後再次調用json 方法來獲得其中返回的JSO N數據。這個方法也是個異步操做。
從以上幾個例子能夠看出,若是單看生成器的代碼,異步操做能夠徹底作的像同步代碼同樣,比 起以前的回調和Promise都要簡單許多。可是,生成器的外部仍是須要作不少事情,好比須要頻 繁調用n ext,若是要作同步效果依然須要嵌套回調函數,代碼依然很複雜。市面也有不少的插件 能夠輔助咱們來執行生成器,好比比較常見的co模塊。它的使用很簡單:
co(getUsers);
引入co模塊後,將生成器傳入它的方法中,這樣它就能自動執行生成器了。關於co模塊這裏我就
再也不多講,有興趣的話能夠參考這篇文章:http://es6.ruanyifeng.eom/#docs/generator-async