0
点赞
收藏
分享

微信扫一扫

【ES】154-重温基础:ES6系列(五)

【ES】154-重温基础:ES6系列(五)_数据结构

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​来实例化。

  1. let p = new Promise(function (resolve, reject){
  2.   if(/*异步操作成功*/){
  3.       resolve(value);
  4.   } else {
  5.       reject(error);
  6.   }
  7. })

Promise​​接收一个函数作为参数,该函数两个参数 ​resolve​​和 ​reject​,有JS引擎提供。

  • resolve​​作用是将 ​​Promise​​的状态从pending变成resolved,在异步操作成功时调用,返回异步操作的结果,作为参数传递出去。
  • reject​​作用是将 ​​Promise​​的状态从pending变成rejected,在异步操作失败时报错,作为参数传递出去。

Promise​​实例生成以后,可以用 ​then​​方法分别指定 ​resolved​​状态和 ​rejected​状态的回调函数。

  1. p.then(function(val){
  2.    // success...
  3. },function(err){
  4.    // error...
  5. })

几个例子来理解

  • 当一段时间过后, ​Promise​​状态便成为 ​​resolved​​触发 ​​then​​方法绑定的回调函数。
  1. function timeout (s){
  2.    return new Promise((resolve, reject){
  3.        setTimeout(result,ms, 'done');
  4.    })
  5. }
  6. timeout(100).then(val => {
  7.    console.log(val);
  8. })
  • Promise​新建后立刻执行。
  1. let p = new Promise(function(resolve, reject){
  2.    console.log(1);
  3.    resolve();
  4. })
  5. p.then(()=>{
  6.    console.log(2);
  7. })
  8. console.log(3);
  9. // 1
  10. // 3
  11. // 2

异步加载图片

  1. function f(url){
  2.    return new Promise(function(resolve, reject){
  3.        const img = new Image ();
  4.        img.onload = function(){
  5.            resolve(img);
  6.        }
  7.        img.onerror = function(){
  8.            reject(new Error(
  9.                'Could not load image at ' + url
  10.            ));
  11.        }
  12.        img.src = url;
  13.    })
  14. }

resolve​函数和 ​reject​函数的参数为 ​resolve​函数或 ​reject​函数
​​p1​​的状态决定了 ​p2​​的状态,所以 ​p2​​要等待 ​p1​的结果再执行回调函数。

  1. const p1 = new Promise(function (resolve, reject) {
  2.  setTimeout(() => reject(new Error('fail')), 3000)
  3. })

  4. const p2 = new Promise(function (resolve, reject) {
  5.  setTimeout(() => resolve(p1), 1000)
  6. })

  7. p2
  8.  .then(result => console.log(result))
  9.  .catch(error => console.log(error))
  10. // Error: fail

调用 ​resolve​或 ​reject​不会结束 ​Promise​参数函数的执行,除了 ​return:

  1. new Promise((resolve, reject){
  2.    resolve(1);
  3.    console.log(2);
  4. }).then(r => {
  5.    console.log(3);
  6. })
  7. // 2
  8. // 1

  9. new Promise((resolve, reject){
  10.    return resolve(1);
  11.    console.log(2);
  12. })
  13. // 1

12.3 Promise.prototype.then()

作用是为 ​Promise​​添加状态改变时的回调函数, ​then​​方法的第一个参数是 ​resolved​​状态的回调函数,第二个参数(可选)是 ​rejected​​状态的回调函数。
​​then​​方法返回一个新 ​Promise​​实例,与原来 ​Promise​​实例不同,因此可以使用链式写法,上一个 ​then​​的结果作为下一个 ​then​的参数。

  1. getJSON("/posts.json").then(function(json) {
  2.  return json.post;
  3. }).then(function(post) {
  4.  // ...
  5. });

12.4 Promise.prototype.catch()

Promise.prototype.catch​​方法是 ​.then(null,rejection)​的别名,用于指定发生错误时的回调函数。

  1. getJSON('/posts.json').then(function(posts) {
  2.  // ...
  3. }).catch(function(error) {
  4.  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  5.  console.log('发生错误!', error);
  6. });

如果 ​Promise​​ 状态已经变成 ​resolved​,再抛出错误是无效的。

  1. const p = new Promise(function(resolve, reject) {
  2.  resolve('ok');
  3.  throw new Error('test');
  4. });
  5. p
  6.  .then(function(value) { console.log(value) })
  7.  .catch(function(error) { console.log(error) });
  8. // ok

当 ​promise​​抛出一个错误,就被 ​catch​方法指定的回调函数捕获,下面三种写法相同。

  1. // 写法一
  2. const p = new Promise(function(resolve, reject) {
  3.  throw new Error('test');
  4. });
  5. p.catch(function(error) {
  6.  console.log(error);
  7. });
  8. // Error: test

  9. // 写法二
  10. const p = new Promise(function(resolve, reject) {
  11.  try {
  12.    throw new Error('test');
  13.  } catch(e) {
  14.    reject(e);
  15.  }
  16. });
  17. p.catch(function(error) {
  18.  console.log(error);
  19. });

  20. // 写法三
  21. const p = new Promise(function(resolve, reject) {
  22.  reject(new Error('test'));
  23. });
  24. p.catch(function(error) {
  25.  console.log(error);
  26. });

一般来说,不要在 ​then​​方法里面定义 ​Reject​​ 状态的回调函数(即 ​then​​的第二个参数),总是使用 ​catch​方法。

  1. // bad
  2. promise
  3.  .then(function(data) {
  4.    // success
  5.  }, function(err) {
  6.    // error
  7.  });

  8. // good
  9. promise
  10.  .then(function(data) { //cb
  11.    // success
  12.  })
  13.  .catch(function(err) {
  14.    // error
  15.  });

12.5 Promise.prototype.finally()

finally​​方法用于指定不管 ​Promise​ 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

  1. promise
  2. .then(result => {···})
  3. .catch(error => {···})
  4. .finally(() => {···});

finally​​不接收任何参数,与状态无关,本质上是 ​then​方法的特例。

  1. promise
  2. .finally(() => {
  3.  // 语句
  4. });

  5. // 等同于
  6. promise
  7. .then(
  8.  result => {
  9.    // 语句
  10.    return result;
  11.  },
  12.  error => {
  13.    // 语句
  14.    throw error;
  15.  }
  16. );

上面代码中,如果不使用 ​finally​​方法,同样的语句需要为成功和失败两种情况各写一次。有了 ​finally​​方法,则只需要写一次。
​​finally​方法总是会返回原来的值。

  1. // resolve 的值是 undefined
  2. Promise.resolve(2).then(() => {}, () => {})

  3. // resolve 的值是 2
  4. Promise.resolve(2).finally(() => {})

  5. // reject 的值是 undefined
  6. Promise.reject(3).then(() => {}, () => {})

  7. // reject 的值是 3
  8. Promise.reject(3).finally(() => {})

12.6 Promise.all()

用于将多个 ​Promise​​ 实例,包装成一个新的 ​Promise​​ 实例,参数可以不是数组,但必须是Iterator接口,且返回的每个成员都是 ​Promise​实例。

  1. const p = Promise.all([p1, p2, p3]);

p​​的状态由 ​p1​​、 ​p2​​、 ​p3​决定,分成两种情况。

  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
  3. // 生成一个Promise对象的数组
  4. const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  5.  return getJSON('/post/' + id + ".json");
  6. });

  7. Promise.all(promises).then(function (posts) {
  8.  // ...
  9. }).catch(function(reason){
  10.  // ...
  11. });

上面代码中, ​promises​​是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成 ​fulfilled​​,或者其中有一个变为 ​rejected​​,才会调用 ​Promise.all​方法后面的回调函数。

注意:如果 ​Promise​​的参数中定义了 ​catch​​方法,则 ​rejected​​后不会触发 ​Promise.all()​​的 ​catch​​方法,因为参数中的 ​catch​​方法执行完后也会变成 ​resolved​​,当 ​Promise.all()​​方法参数的实例都是 ​resolved​​时就会调用 ​Promise.all()​​的 ​then​方法。

  1. const p1 = new Promise((resolve, reject) => {
  2.  resolve('hello');
  3. })
  4. .then(result => result)
  5. .catch(e => e);

  6. const p2 = new Promise((resolve, reject) => {
  7.  throw new Error('报错了');
  8. })
  9. .then(result => result)
  10. .catch(e => e);

  11. Promise.all([p1, p2])
  12. .then(result => console.log(result))
  13. .catch(e => console.log(e));
  14. // ["hello", Error: 报错了]

如果参数里面都没有catch方法,就会调用Promise.all()的catch方法。

  1. const p1 = new Promise((resolve, reject) => {
  2.  resolve('hello');
  3. })
  4. .then(result => result);

  5. const p2 = new Promise((resolve, reject) => {
  6.  throw new Error('报错了');
  7. })
  8. .then(result => result);

  9. Promise.all([p1, p2])
  10. .then(result => console.log(result))
  11. .catch(e => console.log(e));
  12. // Error: 报错了

12.7 Promise.race()

与 ​Promise.all​​方法类似,也是将多个 ​Promise​​实例包装成一个新的 ​Promise​实例。

  1. const p = Promise.race([p1, p2, p3]);

与 ​Promise.all​​方法区别在于, ​Promise.race​​方法是 ​p1​​, ​p2​​, ​p3​​中只要一个参数先改变状态,就会把这个参数的返回值传给 ​p​的回调函数。

12.8 Promise.resolve()

将现有对象转换成 ​Promise​ 对象。

  1. const p = Promise.resolve($.ajax('/whatever.json'));

12.9 Promise.reject()

返回一个 ​rejected​​状态的 ​Promise​实例。

  1. const p = Promise.reject('出错了');
  2. // 等同于
  3. const p = new Promise((resolve, reject) => reject('出错了'))

  4. p.then(null, function (s) {
  5.  console.log(s)
  6. });
  7. // 出错了

注意, ​Promise.reject()​​方法的参数,会原封不动地作为 ​reject​​的理由,变成后续方法的参数。这一点与 ​Promise.resolve​方法不一致。

  1. const thenable = {
  2.  then(resolve, reject) {
  3.    reject('出错了');
  4.  }
  5. };

  6. Promise.reject(thenable)
  7. .catch(e => {
  8.  console.log(e === thenable)
  9. })
  10. // 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​​方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 ​value​​和 ​done​两个属性的对象。

  • value​属性是当前成员的值;
  • done​属性是一个布尔值,表示遍历是否结束;

模拟 ​next​方法返回值:

  1. let f = function (arr){
  2.    var nextIndex = 0;
  3.    return {
  4.        next:function(){
  5.            return nextIndex < arr.length ?
  6.            {value: arr[nextIndex++], done: false}:
  7.            {value: undefined, done: true}
  8.        }
  9.    }
  10. }

  11. let a = f(['a', 'b']);
  12. a.next(); // { value: "a", done: false }
  13. a.next(); // { value: "b", done: false }
  14. a.next(); // { value: undefined, done: true }

13.3 默认Iterator接口

若数据可遍历,即一种数据部署了Iterator接口。
ES6中默认的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​​方法。
  1. let a = new Set().add('a').add('b').add('c');
  2. let [x, y] = a;       // x = 'a'  y = 'b'
  3. let [a1, ...a2] = a;  // a1 = 'a' a2 = ['b','c']
  • (2)扩展运算符
    扩展运算符( ​​...​)也会调用默认的 Iterator 接口。
  1. let a = 'hello';
  2. [...a];            //  ['h','e','l','l','o']

  3. let a = ['b', 'c'];
  4. ['a', ...a, 'd'];  // ['a', 'b', 'c', 'd']
  • (2)yield*
    ​​yield*​后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
  1. let a = function*(){
  2.    yield 1;
  3.    yield* [2,3,4];
  4.    yield 5;
  5. }

  6. let b = a();
  7. b.next() // { value: 1, done: false }
  8. b.next() // { value: 2, done: false }
  9. b.next() // { value: 3, done: false }
  10. b.next() // { value: 4, done: false }
  11. b.next() // { value: 5, done: false }
  12. 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​​方法。
  1. let a = ['a', 'b', 'c'];
  2. for (let k of a){console.log(k)}; // a b c

  3. a.forEach((ele, index)=>{
  4.    console.log(ele);    // a b c
  5.    console.log(index);  // 0 1 2
  6. })

与 ​for...in​​对比, ​for...in​​只能获取对象键名,不能直接获取键值,而 ​for...of​允许直接获取键值。

  1. let a = ['a', 'b', 'c'];
  2. for (let k of a){console.log(k)};  // a b c
  3. for (let k in a){console.log(k)};  // 0 1 2
  • Set和Map
    可以使用数组作为变量,如 ​​for(let[k,v]of b){...}​。
  1. let a = new Set(['a', 'b', 'c']);
  2. for (let k of a){console.log(k)}; // a b c

  3. let b = new Map();
  4. b.set('name','leo');
  5. b.set('age', 18);
  6. b.set('aaa','bbb');
  7. for (let [k,v] of b){console.log(k + ":" + v)};
  8. // name:leo
  9. // age:18
  10. // aaa:bbb
  • 类数组对象
  1. // 字符串
  2. let a = 'hello';
  3. for (let k of a ){console.log(k)}; // h e l l o

  4. // DOM NodeList对象
  5. let b = document.querySelectorAll('p');
  6. for (let k of b ){
  7.    k.classList.add('test');
  8. }

  9. // arguments对象
  10. function f(){
  11.    for (let k of arguments){
  12.        console.log(k);
  13.    }
  14. }
  15. f('a','b'); // a b
  • 对象
    普通对象不能直接使用 ​​for...of​会报错,要部署Iterator才能使用。
  1. let a = {a:'aa',b:'bb',c:'cc'};
  2. for (let k in a){console.log(k)}; // a b c
  3. for (let k of a){console>log(k)}; // TypeError

13.6 跳出for...of

使用 ​break​来实现。

  1. for (let k of a){
  2.    if(k>100)
  3.        break;
  4.    console.log(k);
  5. }

【ES】154-重温基础:ES6系列(五)_数据结构_02


举报

相关推荐

0 条评论