ES6系列目錄
1 let 和 const命令
2 變量的解構賦值
3 字符串的拓展
4 正則的拓展
5 數值的拓展
6 函數的拓展
7 數組的拓展
8 對象的拓展
9 Symbol
10 Set和Map數據結構
11 Proxy
12 Promise對象
13 Iterator和 for...of循環
14 Generator函數和應用
15 Class語法和繼承
16 Module語法和加載實現
所有整理的文章都收錄到我《Cute-JavaScript》系列文章中,訪問地址:http://js.pingan8787.com
12 Promise對象
12.1 概念
主要用途:解決異步編程帶來的回調地獄問題。Promise
簡單理解一個容器,存放着某個未來纔會結束的事件(通常是一個異步操作)的結果。通過 Promise
對象來獲取異步操作消息,處理各種異步操作。
Promise
對象2特點:
對象的狀態不受外界影響。
Promise
對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
注意,爲了行文方便,本章後面的 resolve
d統一隻指 fulfilled
狀態,不包含 rejected
狀態。
Promise
缺點
無法取消Promise,一旦新建它就會立即執行,無法中途取消。
如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
12.2 基本使用
Promise
爲一個構造函數,需要用 new
來實例化。
let p = new Promise(function (resolve, reject){
if(/*異步操作成功*/){
resolve(value);
} else {
reject(error);
}
})
Promise
接收一個函數作爲參數,該函數兩個參數 resolve
和 reject
,有JS引擎提供。
resolve
作用是將Promise
的狀態從pending變成resolved,在異步操作成功時調用,返回異步操作的結果,作爲參數傳遞出去。reject
作用是將Promise
的狀態從pending變成rejected,在異步操作失敗時報錯,作爲參數傳遞出去。
Promise
實例生成以後,可以用 then
方法分別指定 resolved
狀態和 rejected
狀態的回調函數。
p.then(function(val){
// success...
},function(err){
// error...
})
幾個例子來理解 :
當一段時間過後,
Promise
狀態便成爲resolved
觸發then
方法綁定的回調函數。
function timeout (s){
return new Promise((resolve, reject){
setTimeout(result,ms, 'done');
})
}
timeout(100).then(val => {
console.log(val);
})
Promise
新建後立刻執行。
let p = new Promise(function(resolve, reject){
console.log(1);
resolve();
})
p.then(()=>{
console.log(2);
})
console.log(3);
// 1
// 3
// 2
異步加載圖片:
function f(url){
return new Promise(function(resolve, reject){
const img = new Image ();
img.onload = function(){
resolve(img);
}
img.onerror = function(){
reject(new Error(
'Could not load image at ' + url
));
}
img.src = url;
})
}
resolve
函數和 reject
函數的參數爲 resolve
函數或 reject
函數:p1
的狀態決定了 p2
的狀態,所以 p2
要等待 p1
的結果再執行回調函數。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
調用 resolve
或 reject
不會結束 Promise
參數函數的執行,除了 return
:
new Promise((resolve, reject){
resolve(1);
console.log(2);
}).then(r => {
console.log(3);
})
// 2
// 1
new Promise((resolve, reject){
return resolve(1);
console.log(2);
})
// 1
12.3 Promise.prototype.then()
作用是爲 Promise
添加狀態改變時的回調函數, then
方法的第一個參數是 resolved
狀態的回調函數,第二個參數(可選)是 rejected
狀態的回調函數。then
方法返回一個新 Promise
實例,與原來 Promise
實例不同,因此可以使用鏈式寫法,上一個 then
的結果作爲下一個 then
的參數。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
12.4 Promise.prototype.catch()
Promise.prototype.catch
方法是 .then(null,rejection)
的別名,用於指定發生錯誤時的回調函數。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
console.log('發生錯誤!', error);
});
如果 Promise
狀態已經變成 resolved
,再拋出錯誤是無效的。
const p = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
p
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
當 promise
拋出一個錯誤,就被 catch
方法指定的回調函數捕獲,下面三種寫法相同。
// 寫法一
const p = new Promise(function(resolve, reject) {
throw new Error('test');
});
p.catch(function(error) {
console.log(error);
});
// Error: test
// 寫法二
const p = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
p.catch(function(error) {
console.log(error);
});
// 寫法三
const p = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
p.catch(function(error) {
console.log(error);
});
一般來說,不要在 then
方法裏面定義 Reject
狀態的回調函數(即 then
的第二個參數),總是使用 catch
方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
12.5 Promise.prototype.finally()
finally
方法用於指定不管 Promise
對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
finally
不接收任何參數,與狀態無關,本質上是 then
方法的特例。
promise
.finally(() => {
// 語句
});
// 等同於
promise
.then(
result => {
// 語句
return result;
},
error => {
// 語句
throw error;
}
);
上面代碼中,如果不使用 finally
方法,同樣的語句需要爲成功和失敗兩種情況各寫一次。有了 finally
方法,則只需要寫一次。finally
方法總是會返回原來的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
12.6 Promise.all()
用於將多個 Promise
實例,包裝成一個新的 Promise
實例,參數可以不是數組,但必須是Iterator接口,且返回的每個成員都是 Promise
實例。
const p = Promise.all([p1, p2, p3]);
p
的狀態由 p1
、 p2
、 p3
決定,分成兩種情況。
只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
上面代碼中, promises
是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成 fulfilled
,或者其中有一個變爲 rejected
,纔會調用 Promise.all
方法後面的回調函數。
注意:如果 Promise
的參數中定義了 catch
方法,則 rejected
後不會觸發 Promise.all()
的 catch
方法,因爲參數中的 catch
方法執行完後也會變成 resolved
,當 Promise.all()
方法參數的實例都是 resolved
時就會調用 Promise.all()
的 then
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]
如果參數裏面都沒有catch方法,就會調用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報錯了
12.7 Promise.race()
與 Promise.all
方法類似,也是將多個 Promise
實例包裝成一個新的 Promise
實例。
const p = Promise.race([p1, p2, p3]);
與 Promise.all
方法區別在於, Promise.race
方法是 p1
, p2
, p3
中只要一個參數先改變狀態,就會把這個參數的返回值傳給 p
的回調函數。
12.8 Promise.resolve()
將現有對象轉換成 Promise
對象。
const p = Promise.resolve($.ajax('/whatever.json'));
12.9 Promise.reject()
返回一個 rejected
狀態的 Promise
實例。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
注意, Promise.reject()
方法的參數,會原封不動地作爲 reject
的理由,變成後續方法的參數。這一點與 Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
13 Iterator和 for...of循環
13.1 Iterator遍歷器概念
Iterator是一種接口,爲各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。
Iterator三個作用:
爲各種數據結構,提供一個統一的、簡便的訪問接口;
使得數據結構的成員能夠按某種次序排列;
Iterator 接口主要供ES6新增的
for...of
消費;
13.2 Iterator遍歷過程
創建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
第一次調用指針對象的
next
方法,可以將指針指向數據結構的第一個成員。第二次調用指針對象的
next
方法,指針就指向數據結構的第二個成員。不斷調用指針對象的
next
方法,直到它指向數據結構的結束位置。
每一次調用 next
方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含 value
和 done
兩個屬性的對象。
value
屬性是當前成員的值;done
屬性是一個布爾值,表示遍歷是否結束;
模擬 next
方法返回值:
let f = function (arr){
var nextIndex = 0;
return {
next:function(){
return nextIndex < arr.length ?
{value: arr[nextIndex++], done: false}:
{value: undefined, done: true}
}
}
}
let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }
13.3 默認Iterator接口
若數據可遍歷,即一種數據部署了Iterator接口。Symbol.iterator
屬性,即如果一個數據結構具有 Symbol.iterator
屬性,就可以認爲是可遍歷。Symbol.iterator
屬性本身是函數,是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。至於屬性名 Symbol.iterator
,它是一個表達式,返回 Symbol
對象的 iterator
屬性,這是一個預定義好的、類型爲 Symbol 的特殊值,所以要放在方括號內(參見《Symbol》一章)。
原生具有Iterator接口的數據結構有:
Array
Map
Set
String
TypedArray
函數的 arguments 對象
NodeList 對象
13.4 Iterator使用場景
(1)解構賦值
Set
結構進行解構賦值時,會默認調用Symbol.iterator
方法。
let a = new Set().add('a').add('b').add('c');
let [x, y] = a; // x = 'a' y = 'b'
let [a1, ...a2] = a; // a1 = 'a' a2 = ['b','c']
(2)擴展運算符
...
)也會調用默認的 Iterator 接口。
let a = 'hello';
[...a]; // ['h','e','l','l','o']
let a = ['b', 'c'];
['a', ...a, 'd']; // ['a', 'b', 'c', 'd']
(2)yield*
yield*
後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。
let a = function*(){
yield 1;
yield* [2,3,4];
yield 5;
}
let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }
(4)其他場合
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如
newMap([['a',1],['b',2]])
)Promise.all()
Promise.race()
13.5 for...of循環
只要數據結構部署了 Symbol.iterator
屬性,即具有 iterator 接口,可以用 for...of
循環遍歷它的成員。也就是說, for...of
循環內部調用的是數據結構的 Symbol.iterato
方法。使用場景:for...of
可以使用在數組, Set
和 Map
結構,類數組對象,Genetator對象和字符串。
數組
for...of
循環可以代替數組實例的forEach
方法。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
a.forEach((ele, index)=>{
console.log(ele); // a b c
console.log(index); // 0 1 2
})
與 for...in
對比, for...in
只能獲取對象鍵名,不能直接獲取鍵值,而 for...of
允許直接獲取鍵值。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
for (let k in a){console.log(k)}; // 0 1 2
Set和Map
for(let[k,v]of b){...}
。
let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c
let b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb
類數組對象
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o
// DOM NodeList對象
let b = document.querySelectorAll('p');
for (let k of b ){
k.classList.add('test');
}
// arguments對象
function f(){
for (let k of arguments){
console.log(k);
}
}
f('a','b'); // a b
對象
for...of
會報錯,要部署Iterator才能使用。
let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError
13.6 跳出for...of
使用 break
來實現。
for (let k of a){
if(k>100)
break;
console.log(k);
}