• 异步编程的解决方案(执行代码的先后顺序:1.主任务,任务队列:2.微任务,3.宏任务)Promise/Generator/async


    Promise 主要是换种写法而已 (把异步编程变成同步的一种写法)  英文是‘’承诺‘’的意思,表示其它手段无法改变。

    三种状态::pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是Promise这个名字的由来。

    Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected,只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型),如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果,这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    注意:只有 Promise 体内的代码是同步进行的。      Promise:(低版本用不了)

    pending :进行中

    resolve : 决定(成功)

    reject :拒绝(失败)

    finally :成功和失败都会执行(非Promise自带,需自行扩展)

    done:会捕捉到任何可能出现的错误,并向全局抛出。(非Promise自带,按心情自行扩展)

    一. new Promise((resolve,reject)=>{ resolve( ); reject( ) })

     1     let str = 10;
     2     //p是 <promise> 对象 可以进行链式调用,链式调用的返回值是 this (这里指向p,所以可以一直使用 .then( ) 的方法)
     3     let p = new Promise(function (resolve, reject) {
     4       setTimeout(function () {
     5         str = 20;
     6         resolve(str)
     7       }, 100);
     8     });
     9 
    10     p.then((str) => { //这里拿到的 str 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 str
    11       console.log(str) //20
    12       return 5; // 虽然写了 return,但返回的依然是一个新的 promise 对象,而这里的 return 是给下一个.then 传参。
    13     }).then((a) => {
    14       console.log(a) //5
    15     });
    16 
    17     p.then((xxx) => { //这里拿到的 xxx 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 xxx
    18       console.log(xxx) //20
    19     })
    // 先打印 11行的 20, 再输出 18行的 20, 最后输出 14行的 5, 因为 11行 和 18行, 都是最先被执行的 promise, 而14行是一个新的 promise,属于新的微任务,进入异步队列

     

    二. 哪里可以拿到 reject 中的内容

    reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch,没有 then 也会进入 catch

     var p1=new Promise((resolve,rej) => {
        console.log('没有resolve')
        //throw new Error('手动返回错误')
        rej('失败了')
     
     })
     
     p1.then(data =>{
        console.log('data::',data);
     },err=> {
        console.log('err::',err)
     }).catch(
        res => {
        console.log('catch data::', res)
     })
     —————结果————————
      没有resolve
      err:: 失败了

    then中没有第二个回调的情况

     var p1=new Promise((resolve,rej) => {
        console.log('没有resolve')
        //throw new Error('手动返回错误')
        rej('失败了')
     
     })
     
     p1.then(data =>{
        console.log('data::',data);
     }).catch(
        res => {
        console.log('catch data::', res)
     })
    ———————结果———————
    没有resolve
    catch data:: 失败了

    同理: resolve的东西,一定会进入then的第一个回调,肯定不会进入catch

    三. Promise.all ( [ p1 , p2 , ...... ] )

    Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。(all会将传入的数组中的所有promise全部决议以后,将决议值以数组的形式传入到观察回调中,任何一个promise决议为拒绝,那么就会调用拒绝回调。)

    let p1 = new Promise((resolve, reject) => {
      resolve('成功了')
    })
    
    let p2 = new Promise((resolve, reject) => {
      resolve('success')
    })
    
    let p3 = Promse.reject('失败')
    
    Promise.all([p1, p2]).then((result) => {
      console.log(result)               //['成功了', 'success']
    }).catch((error) => {
      console.log(error)
    })
    
    Promise.all([p1,p3,p2]).then((result) => {
      console.log(result)
    }).catch((error) => {
      console.log(error)      // 失败了,打出 '失败'
    })

    Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

    代码模拟:

    let wake = (time) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(`${time / 1000}秒后醒来`)
        }, time)
      })
    }
    
    let p1 = wake(3000)
    let p2 = wake(2000)
    
    Promise.all([p1, p2]).then((result) => {
      console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
    }).catch((error) => {
      console.log(error)
    })
    需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
    这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

    四. Promise.race的使用

    顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      },1000)
    })
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('failed')
      }, 500)
    })
    
    Promise.race([p1, p2]).then((result) => {
      console.log(result)
    }).catch((error) => {
      console.log(error)  // 打开的是 'failed'
    })

    五. Promise是如何捕获异常的?与传统的try/catch相比有什么优势?

    传统的 try / catch 捕获异常方式是无法捕获异步的异常的。

    而对于 Promise 对象来说,构造 Promise 实例时的代码如果出错,则会被认为是一个拒绝的决议,并会向观察回调中传递异常信息。所以即使是一个异步的请求,Promise 也是可以捕获异常的。此外,Promise 还可以通过 catch 回调来捕获回调中的异常。

    六.如果向Promise.all()和Promise.race()传递空数组,运行结果会有什么不同?

    all会立即决议,决议结果是fullfilled,值是undefined

    race会永远都不决议,程序卡住……

    七.扩展一个 Promise.finally()

    扩展一个 Promise.finally(),finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.

    它与done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

    //添加finally方法
    Promise.prototype.finally=function (callback) {
       var p=this.constructor;
       return this.then(//只要是promise对象就可以调用then方法
         value => p.resolve(callback()).then(() => value),
         reason => p.resolve(callback()).then(() => {throw reason})
       );
    }

    对finally方法的理解:

      (1) p.resolve(callback())这句函数callback已经执行
      (2) finally方法return的是一个promise对象,所以还可以继续链式调用其他方法
      (3) 对于Promise.resolve方法: Promise.resolve('foo') 等价于 new Promise(resolve => resolve('foo'));

        所以可以通过then方法的回调函数 接受 实例对象返回的参数
        比如: Promise.resolve(function(){console.log(2);}).then(function(cb){cb()})

      (4) p.resolve(callback()).then(() => value)调用then的目的是给promise实例即this添加成功和失败的回调函数

    Promise.finally() 的使用方法:

      Promise.finally(),按照以上的扩展方式

    八. 扩展一个 Promise.done()

    Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
      Promise.prototype.done = function (onFulfilled, onRejected) {
        this
          .then(onFulfilled, onRejected)
          .catch(function (reason) {
            // 抛出一个全局错误
            setTimeout(() => {
              throw reason
            }, 0)
          })
      }
    从上面代码可见,done方法的使用,可以像then方法那样用,提供fulfilledrejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。

    执行代码的先后顺序:

      先 主任务 后 任务队列。

        任务队列又分:宏任务(task)、微任务(Microtasks)

          微任务(Microtasks)-> Promise 、process.nextTick

          宏任务(task)-> 定时器(比如:setTimeout)、事件(比如:onclick)、整体代码script

    任务队列既有宏任务又有微任务,先执行微任务,再执行宏任务。(1.主任务(主线程)-> 2. 微任务 -> 3.宏任务)。这种循环机制叫做任务循环(event loop)

     


    基于 Promise 之后。又有 Generator ,和它的语法糖:async 

    Generator:函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行),交给 yield 后面的 语句,等此语句执行完,再执行下面的代码。

    协程:

      第一步:协程 开始执行。

      第二步:协程 执行到一半,进入暂停,执行权转移到协程 B

      第三步:(一段时间后)协程 B 交还执行权

      第四步:协程 A 恢复执行。

    也就是说 Promise 和  Generator 一个明显不同的特点是,前者只在Promise体内进行异步代码的同步执行,(不会阻断主任务)

    而后者是阻断(主任务)的执行,等到异步的代码执行完毕之后再执行被阻断的(主任务)代码。

    function* gen(x){
    //这个函数体内任何地方console.log(y)都是undefined
      var y = yield x + 2 ;
      var z = yield x + y ;
      return y;
    };
    let y = gen(5);
    console.log(y.next ()) //  让代码继续执行,并 返回值 拿到 return 后面的 y -> {value: 7, done: false}
    y.next(y.next ( ).value)  //next中可以传入数据(传入的数据为上一次yield表达式的返回值(就是这里的y)),为了下一步操作的运算。

    async:(**现在最常用)

    function fn2(){
        return new Promise(function(resolve){
            setTimeout(function(){
                resolve(50);
            },1000);
        })
    }
    
    async function fn(){
        let y = await fn2() + 2;
        console.log(y);
    }
    fn();//调用后,输出 52 。要 等待 fn2执行完(将异步转成同步的感觉)

     主线程-微任务-宏任务 练习:(如何使用async、await、Promise以及各种任务的执行特点)

    async function fn() {
      console.log(1);
      let b = await fn2();//b拿到return的值 = resolve()里面的值,并阻塞下面的代码。
      console.log(b);
      console.log(2)//出现在await之后,也就是2一定跟着3之后出现
    };
    function fn2() {
      return new Promise((resolve, reject) => {//async、await要配合Promise使用。
          console.log(9);
          setTimeout(() => {
              resolve(3)//进入宏任务,定时器谁先触发谁先执行。
          }, 1000)
      });
    }
    // async function fn() {
    //     //function fn() {
    //     console.log(1);
    //     let b = await fn2();
    //     console.log(2)//进入微任务,谁先进入谁先触发。
    // };
    // function fn2() {
    //     console.log(9);
    //     setTimeout(() => {
    //         resolve(3)//进入宏任务,定时器谁先触发谁先执行。
    //     }, 1000)
    // };
    
    setTimeout(() => {
      console.log(6)//进入宏任务
    }, 500);
    fn()
    let a = new Promise((resolve, reject) => {
      console.log(4);
      resolve()
    })
    a.then(() => {
      console.log(5)//进入微任务
    });
    //fn()
    console.log(8);
    //1,9,4,8,2,5,3,6
    //4,1,9,8,5,2,3,6
    //注:如果await后面的函数,没有被Promise包着。 await 下面的代码会进微任务队列(谁先进入谁先执行),定时器会进宏任务队列(谁先触发谁先执行)。
    //1,9,4,8,5,6,3,2
    //await后面的函数被Promise包着,那么await后面的函数才会阻塞下面的函数,等await后面的函数执行完,再往下执行(只在函数体内发生)。
  • 相关阅读:
    测试方案写作要点
    [loadrunner]通过检查点判定事务是否成功
    【面试】如何进行自我介绍
    【nginx网站性能优化篇(1)】gzip压缩与expire浏览器缓存
    【nginx运维基础(6)】Nginx的Rewrite语法详解
    【PHPsocket编程专题(实战篇①)】php-socket通信演示
    【Linux高频命令专题(22)】gzip
    【nginx运维基础(5)】Nginx的location攻略
    【Linux高频命令专题(21)】df
    【PHPsocket编程专题(理论篇)】初步理解TCP/IP、Http、Socket.md
  • 原文地址:https://www.cnblogs.com/MrZhujl/p/10095875.html
Copyright © 2020-2023  润新知