美文网首页
ES6的异步起始

ES6的异步起始

作者: hellomyshadow | 来源:发表于2019-03-30 15:03 被阅读0次

Promise

  1. Promise是ES6引入的一种异步编程的解决方案;
  2. Promise是一个容器,保存着一个事件的结果,此事件通常是一个未来才会结束的异步操作;
  3. Promise的特点:
    1. 对象的状态不受外界影响;
      1. Promise表示一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
      2. 只有异步操作的结果才可以决定当前处于哪种状态,其他任何操作都无法改变Promise的状态。
    2. 一旦状态变化,就不会再变;
      1. Promise的状态改变只有两种可能:pending-->fulfilledpending-->rejected
      2. 一旦状态变化,就不会在变化了,如果再对Promise添加回调函数,也会立即得到结果。
    3. 无法取消Promise,一旦新建就立即执行,无法中途取消;
    4. 如果不设置回调函数,Promise内部会抛出异常,但不会反映到外部;
    5. 当处于pending状态时,无法得知目前进展到哪一个阶段:刚刚开始还是即将执行完成
  4. 基本使用
    const promise = new Promise(function(resolve, reject) {
        // ... some code
        if (/* 异步操作成功 */){
            resolve(value);
        } else {
            reject(error);
        }
        // ... somo code  --->并不会因为状态被resolve()和reject()修改了而不执行了
    });
    
    1. Promise接受一个函数作为参数,此函数的两个参数resolve、reject也是函数,由JS引擎提供;
    2. resolve()、reject()的作用分别是将pending状态变为resolved、rejected,并将异步操作的结果作为参数传递出去;
    3. Promise新建后会立即执行,然后使用then() 获取执行的状态和结果;
    promise.then(function(res){  -->Promise状态变为resolved时调用,res为 resolve() 传出的参数
        //成功
    }, function(err){   --->可选,Promise状态变为rejected时调用,err为 reject() 传出的参数
        //失败
    });
    
    1. resolve()、reject() 只是修改了Promise的状态,并不会终结Promise参数函数的执行,
      但通常不会在 resolve()、reject() 后再执行代码了。
        new Promise((resolve, reject) => {
            return resolve(1);
            // ... somo code   --> 不会再执行了
        })
    
  5. Promise.prototype.then()
    1. then() 返回一个新的Promise实例,所以支持链式调用
    promise.then(function(res){  --->第一个then
        return res.post;
    }).then(function(post){  -->第二个then
        console.log(post);
    });
    
    1. 第二个 then() 的回调函数中,接收的是第一个then()的回调函数返回的结果;
    2. 如果第一个then()的回调函数返回一个Promise实例,
      那么第二个 then() 会等待这个Promise的状态变化之后,才会执行其回调函数;
    promise.then(res=>{
        return axiosPost(res.post);  ---> axiosPost()返回一个Promise对象
    }).then(post=>{
        console.log(post);  --> axiosPost()的状态变为resolved
    }, err=>{
        console.log(post);  --> axiosPost()的状态变为rejected
    });
    
  6. Promise.prototype.catch()
    1. catch() 其实就是 then(null/undefined, rejected)的别名,单独指定发生错误的回调;
    2. reject() 是手动把状态变为rejected,如果Promise中的异步操作抛出异常,状态也会变为rejected
    const promise = new Promise((resolve, reject) => {
        throw new Error('test');  --> 等效于:reject(new Error('test'));
    });
    promise.catch(function(err) {
        console.log(err);
    });
    
    1. 但是,如果Promise的状态已经变成了resolved,再抛出异常是无效的,因为状态一旦改变就无法修改;
    new Promise(function(resolve, reject) {
        resolve('ok');
        throw new Error('test');  //无效
    }).then(value=>{
        console.log(value);  //执行成功的回调
    }).catch(err => {
        console.log(error);
    });
    
    1. Promise对象的错误具有 冒泡 性质,会一直向后传递,直到被捕获为止;
      也就是说,异步执行失败时,如果前一个then()没有失败的回调函数,则错误会一直向下传递;
      所以,不要在then() 中定义rejected状态的回调函数,而是在最后使用catch() 捕获异常。
    2. then()catch() 也是有顺序的,如果catch() 还有then(),且此then()抛出了异常,那只能等待后面的catch()捕获。
    3. 当然,即使没有catch(),也没有任何rejected状态的回调函数,Promise也不会抛出任何终止程序的异常。
    new Promise(function(resolve, reject) {
        resolve(x + 2);  //x没有声明,报错
    }).then(function() {
        console.log('everything is great');
    });
    setTimeout(() => { console.log(123) }, 2000);  //正常执行
    
    1. Node计划改进Promiserejected状态机制,如果未捕获异常,则终止程序执行。
  7. Promise.prototype.finally()
    1. ES2018 引入的finally(),不管Promise对象最后的状态如何,都会执行;
    promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
    
    1. finally本质上是then方法的特例
    promise.then(result => {
        // 语句
        return result;
    }, error => {
        // 语句
        throw error;
    });
    
  8. Promise.all():将多个 Promise 实例,包装成一个新的 Promise 实例
    1. Promise.all() 接受一个Promise实例的数组作为参数;
    2. 如果参数不是Promise实例,则调用 Promise.resolve() 将其转为Promise实例;
    const p = Promise.all([p1, p2, p3]);
    
    1. p的状态由p1、p2、p3决定:
      1. 只有p1、p2、p3的状态都变成fulfilledp的状态才会变成fulfilled
        p1、p2、p3的返回值组成一个数组,传递给p的回调函数
      2. 只要p1、p2、p3之中有一个被rejectedp的状态就变成rejected
        第一个被reject的实例的返回值,会传递给p的回调函数
    2. 如果p1/p2/p3定义了catch(),那么它一旦被rejected,会返回一个新的Promise实例,并不会触发Promise.all()catch()
  9. Promise.race()
    1. 也是将一个Promise实例的数组包装成一个新的Promise实例;
       const p = Promise.race([p1, p2, p3]);
    
    1. 只要p1、p2、p3中有一个改变状态,p的状态就立即改变。第一个状态发生改变的 Promise实例的返回值,会传递给 p 的回调函数;
  10. Promise.resolve():将现有对象转为 Promise 对象
    Promise.resolve('foo'); --->等价于:new Promise(resolve => resolve('foo'));
    1. 如果参数就是Promise实例,则直接返回;
    2. 如果参数是thenable对象,thenable对象指的是具有then方法的对象
    let thenable = {
        then: function(resolve, reject) {
            resolve(42);
        }
    };
    
    1. Promise.resolve() 会将这个对象转为 Promise 对象,然后立即执行其then()
    2. 此时,Promise对象的状态已经定型了,再使用 then() 时,会立即执行;
    
    let p1 = Promise.resolve(thenable);
    p1.then(value=>{  ------> 立即执行
        console.log(value);  // 42
    });
    
    1. 如果参数是普通对象或不是对象,则将其转为一个状态已是resolvedPromise对象;
    2. 如果不带有任何参数,则直接返回一个resolved状态的 Promise 对象;
    3. 立即resolvePromise 对象,是在本轮 事件循环 结束时,而不是在下一轮 事件循环 开始时。
  11. Promise.reject():返回一个状态为rejectedPromise实例
    1. Promise.resolve() 不同,Promise.reject() 的参数会原封不动的传递给rejected状态的回调函数;
    const thenable = {
        then(resolve, reject) {
            reject('出错');
        }
    };
    Promise.reject(thenable).catch(e => {
        console.log(e === thenable);  //true,说明回调参数e并不是"出错",而是整个对象
    })
    
  12. Promise.try()
    1. 事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块
    Promise.resolve().then(fn);
    
    1. 如果 fn是同步函数,经过Promise包装,fn就变成了异步执行,在本轮事件循环的末尾执行;
    const f = () => console.log('now');
    Promise.resolve().then(f);
    console.log('next');
    // next
    // now
    
    1. 让同步函数同步执行,异步函数异步执行,两种写法:
      1. 第一种
    const f = () => console.log('now');
    (async () => f())();  //立即执行的匿名函数
    console.log('next');
    
    1. async () => f()会吃掉 f() 抛出的错误,要使用promise.catch()捕获错误
    
    (async () => f())()
    .then(...)
    .catch(...)
    
    2. 第二种
    
    const f = () => console.log('now');
    (
    () => new Promise(
        resolve => resolve(f());  //立即执行的匿名函数
    )
    )();
    console.log('next');
    
    1. Promise.try() 就是用来解决此类问题的标准
    const f = () => console.log('now');
    Promise.try(f);
    console.log('next');
    
    1. Promise.try() 为所有操作提供了统一的处理机制,其中一点就是可以更好地管理异常
    database.users.get({id: uId})
    .then(...)
    .catch(...);
    
    1. database.users.get()返回一个 Promise对象,如果抛出异步错误,则用 catch()捕获;
    2. 但如果 database.users.get() 抛出同步错误,则需要 try-catch 捕获
    try {
        database.users.get({id: uId})
        .then(...)
        .catch(...);
    } catch(e){
        ...
    }
    
    1. Promise.try()则可以让promise.catch()统一捕获异常
    Promise.try(() => database.users.get({id: userId}))
    .then(...)
    .catch(...)
    

Iterator

  1. Iterator:遍历器,是一种接口,为各种不同的数据结构提供统一的访问机制;
  2. 任何数据结构只要部署Iterator接口,就可以完成遍历操作,即依次处理该数据结构的所有成员;
  3. Iterator的三种作用
    1. 为各种数据结构,提供一个统一的、简便的访问接口;
    2. 使得数据结构的成员能够按某种次序排列;
    3. Iterator 接口主要供 ES6新增的 for-of消费。
  4. Iterator的遍历过程
    1. 遍历器对象本质上是一个指针对象,指向当前数据结构的起始位置;
    2. 第一次调用指针对象的next(),可以将指针指向数据结构的第一个成员;
    3. 第二次调用指针对象的next(),指针就指向数据结构的第二个成员;
    4. 不断调用指针对象的next(),直到它指向数据结构的结束位置.
    5. 每一次调用 next(),都会返回数据结构的当前成员的信息,一个包含valuedone两个属性的对象;
    6. value属性是当前成员的值,done属性是一个布尔值(表示遍历是否结束);
    7. 模拟 next()
    function makeIter(arr) {
        var nextIndex = 0;
        return {
            next: function() {
            return nextIndex < arr.length ?
                {value: arr[nextIndex++], done: false} : {value: undefined, done: true};
            }
        };
    }
    var it = makeIter(['a', 'b']);
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false }
    it.next() // { value: undefined, done: true }
    
    1. done:falsevalue:undefined 属性是可以省略的.
  5. ES6 规定:默认的Iterator接口部署在数据结构的Symbol.iterator属性上
    1. 也就是说,一个数据结构只要具有Symbol.iterator属性,就可以认为是 可遍历的;
    2. Symbol.iterator属性本身是一个函数,是当前数据结构默认的遍历器生成函数;
    const obj = {
        [Symbol.iterator]: function() { -->让obj变成一个可遍历的对象
            return {
                next: function () {
                    return { value: 1, done: true };
                }
            };
        }
    };
    
    1. 原生具备Iterator 接口(部署了Symbol.iterator属性)的数据结构
      Array,Map,Set,String,TypedArray,arguments,NodeList
    2. 数组的Symbol.iterator属性
    let arr = ['a', 'b'];
    let iter = arr[Symbol.iterator]();
    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: undefined, done: true }
    
  6. 默认调用 Iterator接口的场景
    1. 解构赋值:对数组和Set进行解构赋值时,默认调用Symbol.iterator方法
    let set = new Set().add('a').add('b').add('c');
    let [x, ...rest] = set;
    
    1. 扩展运算符(三点运算符)
    var str = 'hello';
    [...str]  //  ['h','e','l','l','o']
    
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']  //  ['a', 'b', 'c', 'd']
    
    1. yield* 后面跟一个可遍历的结构,会调用遍历器接口
    let generator = function* () {
        yield 1;
        yield* [2,3,4];
        yield 5;
    };
    var iterator = generator();
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
    
    1. 其他
      for-of、Array.from()、Promise.all()、Promise.race()、Map(), Set(), WeakMap(), WeakSet()
  7. 遍历器对象的return(),throw()
    1. 遍历器对象除了具有 next(),还可以具有 return()throw()
    2. 遍历器对象生成函数,next()是必须部署的,return()throw() 可选;
    3. for-of循环提前退出(break语句或者出错),就会调用return()
      如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()
    let readLine = {
        [Symbol.iterator]() {
            return {
                next() {
                    return { done: false };
                },
                return() {
                    file.close();
                    return { done: true };
                }
            };
        }
    };
    for (let line of readLine) {
        console.log(line);
        break;  //break语句
    }
    for (let line of readLine) {
        console.log(line);
        throw new Error();  //抛出异常,终止循环
    }
    
    1. return() 必须返回一个对象,这是 Generator规格决定的;
    2. throw() 主要是配合 Generator 函数使用,一般的遍历器对象用不到。
  8. ES6借鉴 C++、Java、C# 和 Python,引入了for-of,作为遍历所有数据结构的统一方法
    1. 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用 for-of 遍历它的成员;
    2. for-of 内部调用的是数据结构的 Symbol.iterator 方法;
  9. 有些数据结构是在现有数据结构的基础上,计算生成的,ES6的数组、Set、Map都部署了三个方法
    1. entries():返回一个遍历器对象,用来遍历 [键, 值] 组成的数组;
      对于数组,键就是索引值;对于Set,键与值相同;MapIterator接口,默认就是调用entries()
    let arr = ['a', 'b', 'c'];
        for (let pair of arr.entries()) {
        console.log(pair);
    }
    // [0, 'a']
    // [1, 'b']
    // [2, 'c']
    
    1. keys():返回一个遍历器对象,用来遍历所有的键名;
    2. values():返回一个遍历器对象,用来遍历所有的键值;
    3. 这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。
  10. 对于字符串,for-of 能正确识别 32UTF-16 字符;
  11. 伪数组:字符串、DOM NodeList、arguments,可以用for-of遍历
    1. 但并不是所有的伪数组都具备Iterator接口
    let arrayLike = { length: 2, 0: 'a', 1: 'b' };
    // 报错
    for (let x of arrayLike) {
        console.log(x);
    }
    
    1. 最简单的方式是使用 Array.from()将其转为数组
    for (let x of Array.from(arrayLike)) {
        console.log(x);
    }
    
  12. for,for-in,forEach,for-of
    1. for-in 主要为遍历对象而设计,不适用于遍历数组;
    2. forEach 是数组内置的方法,无法中途跳出循环,breakreturn无效;
    3. for-of 支持break、continue、return,循环可遍历对象。

相关文章

网友评论

      本文标题:ES6的异步起始

      本文链接:https://www.haomeiwen.com/subject/ewjrbqtx.html