寫在開頭
ES6
經常使用但被忽略的方法 系列文章,整理做者認爲一些平常開發可能會用到的一些方法、使用技巧和一些應用場景,細節深刻請查看相關內容鏈接,歡迎補充交流。
相關文章
Promise
特色
- 對象的狀態不受外界影響。有三種狀態:
pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
- 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。
Promise
對象的狀態改變,只有兩種可能:從pending
變爲fulfilled
和從pending
變爲rejected
。
使用
Promise
對象是一個構造函數,用來生成Promise實例。構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve
和reject
。resolve
函數的做用是,將Promise
對象的狀態從「未完成」變爲「成功」(即從 pending
變爲 resolved
);reject
函數的做用是,將Promise
對象的狀態從「未完成」變爲「失敗」(即從 pending
變爲 rejected
)。
const promise = new Promise(function(resolve, reject) {
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
複製代碼
Promise
實例生成之後,能夠用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise
對象的狀態變爲resolved
時調用,第二個回調函數是Promise
對象的狀態變爲rejected
時調用。第二個函數是可選的,不必定要提供。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
複製代碼
Promise.resolve()
- 將現有對象轉爲
Promise
對象。
- 不一樣參數狀況:
- 參數是一個
Promise
實例,直接返回這個實例。
- 參數是一個
thenable
對象(thenable
對象指的是具備then方法的對象),將這個對象轉爲 Promise
對象,而後就當即執行thenable
對象的then
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
複製代碼
- 參數不是具備then方法的對象,或根本就不是對象,返回一個新的
Promise
對象,狀態爲resolved
。
const p = Promise.resolve('detanx');
p.then(function (s){
console.log(s)
});
// detanx
複製代碼
- 不帶有任何參數,直接返回一個
resolved
狀態的 Promise
對象。當即resolve()
的 Promise
對象,是在本輪「事件循環」(event loop
)的結束時執行,而不是在下一輪「事件循環」的開始時。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
複製代碼
Promise.reject()
Promise.reject(reason)
方法也會返回一個新的 Promise
實例,該實例的狀態爲rejected
。Promise.reject()
方法的參數,會原封不動地做爲reject
的理由,變成後續方法的參數。 這一點與Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
複製代碼
Promise.prototype.catch()
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名(能夠查看手寫Promise
的該方法實現),用於指定發生錯誤時的回調函數。
// 寫法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 寫法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
複製代碼
- 若是
Promise
狀態已經變成resolved
,再拋出錯誤是無效的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
複製代碼
Promise
對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前面三個Promise產生的錯誤
});
複製代碼
- 通常不要在
then()
方法裏面定義 Reject
狀態的回調函數(即then的第二個參數),老是使用catch
方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
複製代碼
- 跟傳統的
try/catch
代碼塊不一樣的是,若是沒有使用catch()
方法指定錯誤處理的回調函數,Promise
對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯,由於x沒有聲明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
複製代碼
Promise.prototype.finally()
finally()
方法用於指定無論 Promise
對象最後狀態如何,都會執行的操做。 該方法是 ES2018
引入標準的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {
console.log('detanx');
});
// 'detanx'
複製代碼
finally
方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise
狀態究竟是fulfilled
仍是rejected
。方法裏面的操做,應該是與狀態無關的,不依賴於Promise
的執行結果。finally
方法老是會返回原來的值。
// 實現
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
複製代碼
Promise.all()、Promise.race()和Promise.allSettled()
- 相同點
- 方法都是用於將多個
Promise
實例,包裝成一個新的 Promise
實例。
Promise.all()
全部的實例返回都是fulfilled
和Promise.allSettled()
的返回值都是數組。
- 不一樣點
- 有時候,咱們不關心異步操做的結果,只關心這些操做有沒有結束。這時,
Promise.allSettled()
方法就頗有用。若是沒有這個方法,想要確保全部操做都結束,就很麻煩。Promise.all()
方法沒法作到這一點。
const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('全部請求都成功。');
} catch {
console.log('至少一個請求失敗,其餘請求可能還沒結束。');
}
複製代碼
- Promise.all()全部的實例中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值。Promise.allSettled()中的實例fulfilled時,對象有value屬性,rejected時有reason屬性,對應兩種狀態的返回值。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
複製代碼
- 若是做爲參數的
Promise
實例,本身定義了catch
方法,那麼它一旦被rejected
,並不會觸發Promise.all()
的catch
方法。若是實例沒有本身的catch
方法,就會調用Promise.all()
的catch
方法。
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移除
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]
=> // 移除p2的catch
// // Error: 報錯了
複製代碼
Promise.race()
只要有一個實例率先改變狀態,狀態就跟着改變。那個率先改變的 Promise
實例的返回值,就傳遞給回調函數。
const p = Promise.race([1,
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
// 1
複製代碼
Iterator 和 for...of 循環
- ES6-Iterator 和 for...of 循環
- 它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署
Iterator
接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
Iterator
的做用:
- 爲各類數據結構,提供一個統一的、簡便的訪問接口;
- 使得數據結構的成員可以按某種次序排列;
- 新的遍歷命令
for...of
循環,Iterator
接口主要供for...of
消費。
- 遍歷過程:
- 建立一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
- 第一次調用指針對象的
next
方法,能夠將指針指向數據結構的第一個成員。
- 第二次調用指針對象的
next
方法,指針就指向數據結構的第二個成員。
- 不斷調用指針對象的
next
方法,直到它指向數據結構的結束位置。
- 每一次調用
next
方法,都會返回數據結構的當前成員的信息(返回一個包含value
和done
兩個屬性的對象。value
屬性是當前成員的值,done
屬性是一個布爾值,表示遍歷是否結束。)
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
複製代碼
- 對於遍歷器對象來講,
done: false
和value: undefined
屬性都是能夠省略的。
// makeIterator函數能夠簡寫成下面的形式
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
複製代碼
- 使用 TypeScript 的寫法
- 遍歷器接口(
Iterable
)、指針對象(Iterator
)和next
方法返回值的規格能夠描述以下。
interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}
複製代碼
默認 Iterator 接口
- 當使用
for...of
循環遍歷某種數據結構時,該循環會自動去尋找 Iterator
接口。默認的 Iterator
接口部署在數據結構的Symbol.iterator
屬性,或者說,一個數據結構只要具備Symbol.iterator
屬性,就能夠認爲是「可遍歷的」(iterable
)。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
複製代碼
- 原生具有
Iterator
接口的數據結構:Array
、Map
、Set
、String
、TypedArray
、函數的 arguments
對象、NodeList
對象。
- 一個對象若是要具有可被
for...of
循環調用的 Iterator
接口,就必須在Symbol.iterator
的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可)。
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
複製代碼
- 相似數組的對象調用數組的
Symbol.iterator
能夠遍歷;普通對象部署數組的Symbol.iterator
方法,並沒有效果。
// 相似數組對象
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
// 普通對象,下標不是數字
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
複製代碼
- 若是Symbol.iterator方法對應的不是遍歷器生成函數(即會返回一個遍歷器對象),解釋引擎將會報錯。
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
複製代碼
調用 Iterator 接口的場合
- 解構賦值
- 對數組和
Set
結構進行解構賦值時,會默認調用Symbol.iterator
方法。
- 擴展運算符
- 擴展運算符(
...
)也會調用默認的 Iterator
接口。
yield*
yield*
後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。
- 其餘
for...of
、Array.from()
、Map(), Set()
, WeakMap()
, WeakSet()
(好比new Map([['a',1],['b',2]])
)、Promise.all()
、Promise.race()
...
遍歷器對象的 return()和throw()
- 遍歷器對象除了具備
next
方法,還能夠具備return
方法和throw
方法。若是你本身寫遍歷器對象生成函數,那麼next
方法是必須部署的,return
方法和throw
方法是否部署是可選的。
for...of
循環提早退出(一般是由於出錯,或者有break
語句),就會調用return
方法。若是一個對象在完成遍歷前,須要清理或釋放資源,就能夠部署return
方法。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
// 觸發return狀況一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 觸發return狀況二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
複製代碼
throw
方法主要是配合 Generator
函數使用,下一彈會提到。
for...of 循環
- 做爲遍歷全部數據結構的統一的方法。
- 和其餘遍歷對比
for...of
會正確識別 32
位 UTF-16
字符。有着同for...in
同樣的簡潔語法,可是沒有for...in
那些缺點。不一樣於forEach
方法,它能夠與break
、continue
和return
配合使用。
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
複製代碼
forEach
沒法中途跳出forEach
循環,break
命令或return
命令都不能奏效。
for...in
循環缺點,一是數組的鍵名是數字,可是for...in
循環是以字符串做爲鍵名「0
」、「1
」、「2
」等等。二是for...in
循環不只遍歷數字鍵名,還會遍歷手動添加的其餘鍵,甚至包括原型鏈上的鍵。最後某些狀況下,for...in
循環會以任意順序遍歷鍵名。for...in
循環主要是爲遍歷對象而設計的,不適用於遍歷數組。
- 其餘類型的數據可使用其餘技巧轉爲有
Iterator
接口的數據結構再使用for...of
進行遍歷。
- 對象
- 使用
Object.keys
方法將對象的鍵名生成一個數組,而後遍歷這個數組。
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
複製代碼
- 使用
Generator
函數將對象從新包裝一下。
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
複製代碼
- 相似數組的對象但不具備
Iterator
接口
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 報錯
for (let x of arrayLike) {
console.log(x);
}
// 正確
for (let x of Array.from(arrayLike)) {
console.log(x);
}
複製代碼