zoukankan      html  css  js  c++  java
  • js之promise&geneator

      一、Promise 

      Promise代表承诺是一个类。它用于表示一个异步操作的最终完成或失败及其结果值。

        当我们new一个Promise时,它里面必须有一个可执行函数executor作为参数,这个executor是立即执行的。executor函数中一般用来管理一个异步编程代码(当然同步代码也是可以的)并且会给executor函数传递resolve/reject这两个函数作为参数。异步操作成功执行resolve,修改promise实例状态为成功,传递的值就是 [[PromiseResult]](默认是undefined,成功的结果)。异步操作失败执行reject,修改promise实例的状态为失败,传递的值也是 [[PromiseResult]](失败的原因)。只要promise的状态发生改变那么再执行其任何操作状态不会发生改变。

    let p = new Promise((resolve,reject)=>{
      console.log(2);
    });
    console.log(1);// 执行顺序是2 1 从中我们可以看到promise是同步的
    
    // new Promise((resolve, reject)=>{resolve(value)}) resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变
    
    // new Promise((resolve, reject)=>{reject(reason)}) reject为失败,接收参数reason,状态改变为rejected,不可再次改变
    
    // 若是executor函数报错 直接执行reject(); 

      从上面代码可以看出p是Promise类的一个实例,这里面有一些内置的私有属性和公共方法从中我们可以看到有catch,finally,then方法。如下所示:

    • 内置私有属性:
      • [[PromiseState]]实例状态:pending / fulfilled / resolved
      • [[PromiseResult]]实例的值
    • 公共方法:
      • then:为 Promise 实例添加状态改变时的回调函数。
      • catch:.then(null,rejected)或.then(undefined,rejection)的别名,用于指定发生错误时的回调函数。
      • finally:不管 Promise 对象最后状态如何,都会执行的操作。

        

       从上面可以看出Promise有.then方法,.then方法里面也有两个函数(onfulfilledCallback,onrejectedCallback)作为参数,实例状态改变执行then方法时存放的两个方法中的某一 个方法执行:

    • 当状态state为fulfilled,则执行onfulfilledCallback,传入this.value。当状态state为rejected,则执行onrejectedCallback,传入this.reason
    • onfulfilledCallback,onrejectedCallback是函数,它们需要分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数
    • 执行p.then时首先去把传递进来的onfulfilledCallback,onrejectedCallback存储在一个容器中然后去验证当前实例的状态
      • 当实例状态为pending不做任何处理
      • 如果已经变为fulfilled/rejected则会通知对应的回调函数执行,但是不是立即执行,而是将其放置在EventQueue中的微任务队列中
      Promise本身不是异步而是管控异步编程它里面的then方法是异步微任务可以分为以下几步:   
       1. new Promise 时创建一个promise实例,此时在executor函数中管理一套异步代码;
                2.后期等异步操作成功或者失败时,执行resolve/reject以此来控制promise实例的状态和结果;
                3.根据状态和结果就可以控制基于.then注入的两个方法中的哪一个去执行了;
    let p = new Promise((resolve, reject) => {
    console.log(1); setTimeout(() => { resolve('ok');
       console.log(4); }, 1000);
     console.log(2); });
    console.log(p); p.then(result => { console.log('成功', result); }, reason => { console.log('失败', reason); }) console.log(3);
      在上面的代码中我们可以看到,代码自上而下运行:
    • new Promise时生成一个promise实例p,立即执行executor函数,打印1;
    • executor里面有一个setTimeout定时器,它是一个异步宏任务,将其放到EventQuene中;
    • 代码向下,打印2;
    • 继续向下,打印p,这时p是一个promise实例,它里面的状态没有发生改变还是pending;
    • 执行p1.then注入两个方法,这两个方法会保存起来,放置在EventQueue中的异步微任务队列中;
    • 执行同步代码,打印“3”;
    • 1000ms后执行定时器里的回调函数,改变promise状态和值,状态从"pending" -> "fulfilled","value "-> “ok”。通过之前基于then存放的方法执行(异步微任务);
    • 先要执行同步代码,打印4;
    • 执行.then里面的onfulfilledCallback函数打印"成功 ok";
    • 所以执行顺序是:” 1 “ =>” 2 “ => ”Promise{<pending>} “ =>” 3 “ =>” 4 “ => ”成功 ok“
    let p1 = Promise.resolve('ok')
    let p2 = p1.then(result => {
        console.log('成功', result); //成功->'ok'
    }, reason => {
        console.log('失败', reason);
    })
    console.log(p2); //Promise {<pending>}
    
    
    p2.then(result => {
        console.log('成功', result);
    }, reason => {
        console.log('失败', reason);
    })
    console.log(p2);
      执行.then返回一个全新的promise实例,所以可以采用链式写法,即then方法后面可以调用另外一个then。对于p2来说,不论执行的是p1.then存放的onfulfilledCallback/onjectedCallback两个方法中哪一个
    • 只要是方法执行报错:那么[[PromiseState]]是rejected,[[PromiseResult]]报错的原因;
    • 只要是方法执行不报错:
      • 方法中返回的是一个全新的promise实例,那么“全新的promise实例”的成功和失败决定p2的成功与否;
      • 如果返加的不是一个全新的promise实例,那么[[PromiseState]]是fulfilled,[[PromiseResult]]成功的结果;
            Promise.reject('no')
                .then(result => {
                    console.log('成功->', result);
                    return 10;
                }/*reason=>{return Promise.reject(reason);}*/)
                .then(null, reason => {
                    console.log('失败', reason);  //失败 no
                })
    
            Promise.resolve('ok')
                .then(null/* result => {return result;} */, reason => {
                    console.log('失败->', reason);
                })
                .then(result => {
                    console.log('成功->', result); // 成功-> ok
                })
      真实项目中在多个then链下,then方法基本都存放的是成功处理的事情,最后catch一下。如果onfulfilledCallback/onrejectedCallback不传递,那么状态和结果都是都会"顺延/穿透"到下一个同等状态应该执行的回调函数上。
        Promise.resolve('ok')
                .then(result => {
                    console.log('成功', result); //成功 ok
                    return 10;
                },null/*reason=>Promise.reject(reason)*/)
                .then(result => {
                    console.log('成功', result); // 成功 10
                    return 20;
                })
                .then(result => {
                    console.log('成功', result); // 成功 20
                    return Promise.reject('no');
                })
                .then(result => {
                    console.log('成功', result); 
                    return Promise.resolve('yes'); // Promise {<fulfilled>: undefined}  
                })
                // .then(null/*result => result*/, reason => {
                //     console.log('失败', reason); // 失败 no
                // })
                //可以用catch来代替
                .catch(reason => {
                    console.log('失败', reason); // 失败 no
                })
      promise里面还有其它一些常用方法如下所示:
        promise.all([])要求数据中的每一项尽可能都是promise,它也是返回一个promise实例。等待所有的promise实例都成功整体返回的状态才是成功,只要有一个失败整体状态就是失败。
        promise.race()看多个实例谁先处理完,先处理完成的状态(不论是失败还是成功)就是最后整体的状态。  
        promise.all()方法中,最后的结果顺序和传递数组的顺序是一样的。处理过程如下:
    function fn(interval) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(interval);
            }, interval);
        })
    }
    let p1 = fn(3000);
    let p2 = fn(1000);
    //let p3 = Promise.resolve(0);
    let p3 = Promise.reject("no"); Promise.all([p1, p2, p3]).then(results => {
    // 结果的顺序和传递数组的顺序要保护一致 console.log(results) }).catch(reason => {
    // 处理过程中,遇到一个失败则all立即为失败,结果就是失败的原因
      console.log(reason);
    })
      二、async/await  
      在平时的项目中是解决异步编程, 我还们还要用到async/await。async/await可以让我们用一种更简洁的方式写出基于promise的异步行为,而不用刻意的链式调用promise。  
      async它是一个函数修饰符,可以控制函数返回promise实例。函数执行报错实例状态是失败,结果是报错原因;否则实例状态是成功,结果是return后面的值。如果函数内部做了异步捕获,则不是成功的状态。
      async一般配合await使用,await后面要放置一个promise,如果写的是其它如普通数字,那也会将其变成promise实例。await表达式会暂停整个async函数的执行进程并出让其控制权也就是说await中断函数体中其下面的代码执行,只有等待后面promise实例是成功状态后才会把之前暂停的代码执行。如果后面的promise实例是失败的,则下面的代码不会再执行。总结来说,因为await是一个异步微任务,函数体中遇到await会把awiat后面的代码立即执行并且await下面的代码会暂停执行放置在EventQueue的微任务队列中。
    async function foo() {
        await 1
    }
    //  等价于
    
    function foo() {
        return Promise.resolve(1).then(() => undefined)
    }
        function computed() {
          console.log(1);
          return new Promise(resolve => {
               setTimeout(() => {
                  resolve(2);
               }, 1000);
           })
        };
       console.log(3);
       async function fn() {
         console.log(4);
         let result = await computed();
         console.log(result);
          console.log(5);
        }
        fn();
        console.log(6);
    • 代码自上下而执行,先是定义computed函数;
    • 第二步,主线程中,打印"3";
    • 定义async fn()
    • fn()函数执行代码向下,打印" 6 "; 主线程为空,因为eventloop机制先执行异步微任务,但此时异步微任务依赖宏任务,所以先执行宏任务
      • 打印“ 4 ”;
      • let result = await computed(),这时需要先执行computed
      • 打印" 1 "; 
      • 返回一个promise,设置了定时器,把定时器的回调函数放入到异步宏任务中1000ms后执行回调函数
      • 再把await下面的代码存储到异步微任务中,等待promise状态变为成功就触发微任务执行
    • 1000ms,执行resolve(2),promise成功
    • 执行微任务 打印 “ 2 ”;
    • 代码向下打印“ 5 ”
    • 所以最终结果是:3 4 1 6 2 5 
     三、Iterator  
      Iterator(遍历器)它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。从上面的解释来看我们知道了Iterator 有如下作用:
    1. 为各种数据结构,提供一个统一的、简便的访问接口;
    2.  使得数据结构的成员能够按某种次序排列,可以基于for/of进行循环遍历;
      Iterator遍历其实就是创建一个指针对象,指向当前数据结构的起始位置。也就是说遍历器对象本质上就是一个指针对象。如下所示:
    1. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员;
    2. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员;
    3. 不断调用指针对象的next方法,直到它指向数据结构的结束位置;
      每一次调用next方法,都会返回数据结构的当前成员信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
    var it = makeIterator([10, 20, 30]);
    console.log(it.next())// { value: 10, done: false }
    console.log(it.next())// { value: 20, done: false }
    console.log(it.next())// { value: 30, done: false }
    console.log(it.next())// { value: undefined, done: true }
    
    function makeIterator(array) {
        var index = 0;
        return {
            next: function () {
                //value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束
                return index < array.length ? 
                    { value: array[index++], done: false } :
                    { value: undefined, done: true };
                // 其实对于遍历器对象来说,done: false和value: undefined属性都是可以省略
                // return index <array.length?{value:array[index++]}:{done:true}
            }
        };
    }
      虽然浏览器没有内置的Iterator,但是它给很多数据结构都提供了迭代的接口方法Symbol.iterator。原生具备Iterator接口的数据结构有Array,Map,Set,String,函数的arguments及NodeList等等,但是Object.prototype上是不具备Symbol.iterator。对象之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
    let obj = { name: 'davina', age: 20, 0: 10 };
    Object.prototype[Symbol.iterator] = function values() {
        let self = this,
            index = 0,
            // Object.keys()方法返回对象的所有可枚举属性的字符串数组
            // Object.getOwnPropertySymbols()方法返回对象自身上找到的所有 Symbol 属性的数组
            keys = Object.keys(self).concat(Object.getOwnPropertySymbols(self));
        return {
            next() {
                if (index > keys.length - 1) {
                    return {
                        done: true,
                        value: undefined
                    }
                }
                let key = keys[index++];
                return {
                    done: false,
                    value: self[key]
                }
            }
        }
    }
    for (let item of obj) {
        console.log(item);
    }
      在真实的开发中,如果是类数组对象,我们可以直接借用数组或者是字符串上原型上的Symbol.iterator当然也可以按照上面直接写一个方法。如下所示:
    let obj = {
        0: 10,
        1: 20,
        length: 2
    };
    Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    for (let item of obj) {
        console.log(item); // 10 20 
    }
     四、Generator  
      Generator函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,也就是说Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator函数内部的每一个状态。  
      形式上Generator函数是一个普通函数,但是有两个特征:
    • function关键字与函数名之间有一个星号;
    • 函数体内部使用yield表达式,定义不同的内部状态。
    function* generator() {
        console.log('xxxxxxxx');
    }
    generator.prototype.name = 'davina';
    // let iter = new generator(); //VM2389:5 Uncaught TypeError: generator is not a constructor 不用new直接创建func/Generator类的实例
    let itor = generator();
    
    console.log(itor); //generator
    console.log(itor instanceof generator); //true
    console.log(itor.__proto__ === generator.prototype)//true
    console.log(({}).toString.call(itor)); //[object Generator]
      从上面的代码可以看出Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。但调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,即返回的结果是把自己当做类,创建一个实例“迭代器对象”。  
      实例常用的方法有next()、return()和throw()。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式如下所示:
    function* func() {
        console.log('a'); 
        yield 1;
        console.log('b');
        yield 2;
        console.log('c');
        yield 3;
        console.log('d');
        return 4;
    }
    let itor = func(); 
    //每次执行next遇到一个yield就结束 ,每一次返回的结果是符合迭代器规范的{done:true/false,value:yield后面的值或者函数的返回值
    console.log(itor.next());  //{ value: 1, done: false }   
    console.log(itor.next());  //{ value: 2, done: false }
    //return方法 可以返回给定的值,并且终结遍历 Generator 函数。
    console.log(itor.return('return'));  //{ value: return, done: true }
    //throw方法会直接抛出异常信息,下面代码不在执行
    console.log(itor.throw("xxxxxxxx"));  //Uncaught xxxxxxxx
    console.log(itor.next()); 
      next()方法里还可以传递其它的参数。每一次next传递的值都是作为上一次yield的返回结果。如下所示:
    function* generator() {
        console.log('a');
        let a = yield 1;
        console.log('b', a);
        let b = yield 2;
        console.log('c', b);
    }
    let itor = generator();
    //第一次传递的值没有用
    console.log(itor.next(10));  // a  {value: 1, done: false}
    console.log(itor.next(100)); // b 100 {value: 2, done: false}
    console.log(itor.next(200)); // c 200 {value: undefined, done: true}
    function* generator1() {
        yield 1;
        yield 2;
    }
    function* generator2() {
        yield 3;
        //yield可以进入到其它生成器中进行迭代
        yield* generator1();
        yield 4;
    }
    let itor = generator2();
    console.log(itor.next());// {value: 3, done: false}
    console.log(itor.next());// {value: 1, done: false}
    console.log(itor.next());// {value: 2, done: false}
    console.log(itor.next());// {value: 4, done: false}
    console.log(itor.next());// {value: undefined, done: true}









     

     

  • 相关阅读:
    jieba库与词云的使用——以孙子兵法为例
    Python 计算π及进度条显示
    风车
    UDP协议编程
    matplotlib的基本使用、绘制雷达图及绘制数学函数
    numpy库的学习笔记及自定义手绘风
    PIL库的学习总结及生成GIF
    预测球队比赛结果及利用pyinstaller打包文件
    汉诺塔的实现
    有进度条的圆周率计算
  • 原文地址:https://www.cnblogs.com/davina123/p/14120661.html
Copyright © 2011-2022 走看看