重溫基礎:ES6系列(五)

640?wx_fmt=png

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)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

注意,爲了行文方便,本章後面的 resolved統一隻指 fulfilled狀態,不包含 rejected狀態。

Promise缺點

  • 無法取消Promise,一旦新建它就會立即執行,無法中途取消。

  • 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。

  • 當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

12.2 基本使用

Promise爲一個構造函數,需要用 new來實例化。

let p = new Promise(function (resolve, reject){	
   if(/*異步操作成功*/){	
       resolve(value);	
   } else {	
       reject(error);	
   }	
})

Promise接收一個函數作爲參數,該函數兩個參數 resolvereject,有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

調用 resolvereject不會結束 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的狀態由 p1p2p3決定,分成兩種情況。

  1. 只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

  2. 只要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遍歷過程

  1. 創建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。

  2. 第一次調用指針對象的 next方法,可以將指針指向數據結構的第一個成員。

  3. 第二次調用指針對象的 next方法,指針就指向數據結構的第二個成員。

  4. 不斷調用指針對象的 next方法,直到它指向數據結構的結束位置。

每一次調用 next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含 valuedone兩個屬性的對象。

  • 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可以使用在數組 SetMap結構類數組對象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和Mapfor(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);	
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章