异步编程--Promise->Generator->Async/Await->异步生成器

Promise

在这里插入图片描述

主要API和属性

Promise 构造函数: Promise (excutor) {}

  • 同步函数,是个执行器,会立即执行

Promise.prototype.then 方法: (onResolved, onRejected) => {}

  • 根据状态去执行不一样的回调函数

Promise.prototype.catch 方法: (onRejected) => {}

  • Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。

Promise.resolve 方法: (value) => {}

  • 默认产生一个成功的 promise。

Promise.reject 方法: (reason) => {}

  • 默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果

Promise.all 方法: (promises) => {}

  • promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。

Promise.race 方法: (promises) => {}

  • Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)

属性

  • PromiseStatus

    • pending
    • resolved
    • rejected
  • PromisResult

关键问题

Promise从两眼发懵到双眼放光(3)-Promise的几个关键问题(一)

Promise从两眼发懵到双眼放光(4)-Promise的几个关键问题(二)

如何改变 promise 的状态?

  • 当执行resolve(value)时,状态会从pending变为resolved
    当执行reject(reason)或者抛出Error时,状态会从pending变为rejected

一个 promise 指定多个成功/失败回调函数, 都会调用吗?

  1. 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
  2. 如何先改状态再指定回调?
    1). 在executor中直接调用resolve()/reject()
    2). 延迟更长时间才调用then()
  3. 什么时候才能得到数据?
    1). 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
    2). 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

改变 promise 状态和指定回调函数谁先谁后?

  • 改变promise状态先因为回调函数是异步的

promise.then()返回的新 promise 的结果状态由什么决定?

    1. 简单表达: 由then()指定的回调函数执行的结果决定
  1. 详细表达:
    1). 如果抛出异常, 新promise变为rejected, reason为抛出的异常
    2). 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值
    3). 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果

promise 如何串连多个操作任务?

  • promise的then()返回一个新的promise, 通过then的链式调用串连多个同步/异步任务

promise 异常传透?

    1. 当使用promise的then链式调用时, 可以在最后指定失败的回调,
  1. 前面任何操作出了异常, 都会传到最后失败的回调中处理

中断 promise 链?

如何中断Promise的链式调用

  • 通过抛出一个异常来终止

  • 通过reject来中断

  • 在回调函数中返回一个pending状态的promise对象

    • new Promise((resolve, reject) => {  
      resolve(1)  
      }).then(  
      value => {  
      console.log('onResolved1()', value)  
      return new Promise(() => {}) // 返回一个pending的promise  中断promise链  
      },  
      ).then(  //不会进入这个回调
      value => {  
      console.log('onResolved2()', value)  
      },  
      reason => {  
      console.log('onResolved2()', reason)  
      },  
      )
      

同步迭代器接收异步任务的时候怎么办?

  • 利用观察者模式,因为是异步,状态还是pending,所以先进行订阅者订阅,被动将异步函数push进消息队列,在reject,resolved进行发布,也就是将消息队列进行遍历,实现异步任务调用

基本特征

Promise/A+

  1. promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」

  2. new promise时, 需要传递一个executor()执行器,执行器立即执行

  3. executor接受两个参数,分别是resolve和reject

  • try{
      //同步调用『执行器函数』
    executor(resolve, reject);
    }catch(e){
        //修改 promise 对象状态为『失败』
        reject(e);
    }
    
  1. promise 的默认状态是 pending;
  • this.PromiseState = ‘pending’;
  1. promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」

  2. promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5

  3. promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变

  4. promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」

  5. 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value

  6. 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason

  7. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;

  8. 异步操作——利用观察者模式

  • 因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调

    • if(this.PromiseState === 'pending'){
          //保存回调函数
          this.callbacks.push({
              onResolved: function(){
                  callback(onResolved);
              },
              onRejected: function(){
                  callback(onRejected);
              }
          });
      }
      
  1. then 的链式调用
  • promise 的优势在于可以链式调用。在我们使用 Promise 的时候,当 then 函数中 return 了一个值,不管是什么值,我们都能在下一个 then 中获取到,这就是所谓的then 的链式调用。
  1. 值穿透特性
  • 而且,当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的值的穿透。

  • then 的参数 onFulfilled 和 onRejected 可以缺省,如果 onFulfilled 或者 onRejected不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;

  • //判断回调函数参数
    if(typeof onRejected !== 'function'){
        onRejected = reason => {
            throw reason;
        }
    }
    if(typeof onResolved !== 'function'){
        onResolved = value => value;
    
        //value => { return value};
    
    }
    
  1. promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise";「规范 Promise/A+ 2.2.7」

  2. 如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;

  3. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」

  4. 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」

  • //封装函数
    function callback(type){
    try{
    //获取回调函数的执行结果

            let result = type(self.PromiseResult);
            console.log("result---"+result)
            //判断
            if(result instanceof Promise){
                //如果是 Promise 类型的对象
                result.then(v => {
                    console.log('sus')
                    resolve(v);
                }, r=>{
                    console.log('err')
                    reject(r);
                })
            }else{
                //结果的对象状态为『成功』
                resolve(result);
            }
        }catch(e){
            reject(e);
        }
    }
    
  1. 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」

  • if(typeof onRejected !== 'function'){
        onRejected = reason => {
            throw reason;
        }
    }
    

代码仓库

Promise个人整理

9k字 | Promise/async/Generator实现原理解析

Promise之你看得懂的Promise

你能手写一个promise吗

Generator

什么是Generator 如何定义

Generator 是一个带星号的“函数”(它并不是真正的函数,下面的代码会为你验证),可以配合 yield 关键字来暂停或者执行函数。

function* gen() {

  console.log("enter");

  let a = yield 1;

  let b = yield (function () {return 2})();

  return 3;

}

var g = gen()           // 阻塞住,不会执行任何语句

console.log(g.next())

console.log(g.next())

console.log(g.next())

console.log(g.next()) 

// output:

// { value: 1, done: false }

// { value: 2, done: false }

// { value: 3, done: true }

// { value: undefined, done: true }

是怎么工作的

  • 生成器不一定会执行生成器函数体,通过创建迭代器对象,可以与生成器通信
  • 迭代器用于控制生成器的执行。迭代对象暴露的最基本接口是next方法迭,这方法可以用来向生成器请求一个值, 从而控制生成器
  • next函数调用后,生成器就开始执行代码,当代码执行到yield关键字时,就会生成一个中间结果(生成值序列中的一项),然后返回一一个**新对象,**其电封装了结果健一个指示完成的指示器。
    在这里插入图片描述
  • 每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值滑求的到达。这是普通函数完全不具有的强大特性,后续的例子中它还会起到更大的作用。

返回值

Promise对象又分两种方式

  1. return new Promise((resolve,reject){resolve()})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGtLPZVu-1616849620185)(/Users/macos/Documents/photo/image-20210324211252242.png)]

  1. async function(){ await 异步函数/对象}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KAfw7ur3-1616849620187)(/Users/macos/Documents/photo/image-20210324212033719.png)]

对于非promise对象

我们可以直接返回,也可以通过next(…),并在本质上要求调用代码为 yield表达式提供一个结果值。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbON7iMH-1616849620188)(/Users/macos/Documents/photo/截屏2021-03-24 下午8.57.21.png)]

Generator的内部构成

我们已经知道了调用一个生 成器不会实际执行它。相反,它创建了一一个新的迭代器,通过该迭代器我们才能从生成器中请求值在生成器生成(或让渡)了一个值后,生成器会挂起执行并等待下一一个请求的到来。在某种方面来说,生成器的工作更像是一个小 程序,一个在状态中运 动的状态机。

  • 挂起开始创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。

  • 执行生成器中的代码执行的状态。执行要么是刚开始,要么是从上次挂起的时候继续的。当生成器对应的迭代器调用了next方法,并且当前存在可执行的代码时,生成器都会转移到这个状态,

  • 挂起让渡——当生成器在执行过程中遇到了一个yield表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。

  • 完成——在生成器执行期间,如果代码执行到return 语句或者全部代码执行完毕,生成器就进入该状态。
    在这里插入图片描述

与生成器的交互

  1. 作为生成器函数参数发送值
  2. 使用next方法向生成器发送值

在这里插入图片描述

在这里插入图片描述

yield基本介绍

yield 同样也是 ES6 的新关键词,配合 Generator 执行以及暂停。yield 关键词最后返回一个迭代器对象,该对象有 value 和 done 两个属性,其中 done 属性代表返回值以及是否完成。yield 配合着 Generator,再同时使用 next 方法,可以主动控制 Generator 执行进度。

Generator的工作机制是什么呢?因为在js里面是单线程,会先生成一个生成器,执行权一开始在生成器,遇到yield关键字,生成器会让步,状态变成暂时挂起,执行权交于迭代器,迭代器通过next()通知生成器恢复执行权,这时候要想的是返回什么?这里有两种返回结果,一种是promise对象,一种是非promise对象。

对于promise对象,我们通过回调函数,将返回结果进行打印,同时可以控制执行器的发起,这里就涉及成功回调和错误抛出

Async/await

MDN_Async

Async/await的工作原理

  • 一句话,async 函数就是 Generator 函数的语法糖
  • 一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
  • async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中

异步生成器

异步编程代码仓库

生成器可以 yield 一个 promise,然后这个 promise 可以被绑定,用其完成值来恢复这个生成器的运行。

异步,生成器,迭代器如何运行?这个魔法如何实现?

我们需要一个可以运行生成器的运行器(runner),接受一个yield 出来的 promise,然后将其连接起来用以恢复生成器,方法是或者用完成成功值,或者用拒绝原因值抛出一个错误到生成器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOpWj8oI-1616849620190)(/Users/macos/Documents/photo/image-20210325095328275.png)]

Promise + Generator

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWpQQKiH-1616849620191)(/Users/macos/Documents/photo/image-20210324214105035.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYobosXJ-1616849620191)(/Users/macos/Documents/photo/image-20210324214126727.png)]

Async/await+Generator

首先要明白async/await的工作机制

async会返回一个promise对象,而这个await的作用就是返回成功的回调函数,所以这里不需要then。这里也很很好地说明了有async必有await。

在Async/await+Generator的生成器对象也就是迭代器是一个异步生成器对象,也就是说和一般的生成器不同,所以一般用for…of不能将生成器进行迭代,这里有两种方法,一种是手写迭代器,一种asnyc➕for await …of进行迭代

手写迭代器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pxvUgcQ7-1616849620192)(/Users/macos/Documents/photo/image-20210325091255540.png)]

因为一般的生成器对象用next()方法可以返回一个带有value和done属性的对象,我们可以根据这个done去判断是否继续迭代。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtrNh9Kq-1616849620193)(/Users/macos/Documents/photo/image-20210325091147060.png)]

asnyc➕for await …of

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rylQ9PsO-1616849620193)(/Users/macos/Documents/photo/image-20210325091446035.png)]

异步生成器和异步迭代

阮一峰

观察者模式

订阅/注册(resigter)则为被动->订阅者,发布(fire)则为主动->观察者

这里需要注意的是要先订阅,将消息类型先push进去,然后发布者再进行遍历

在这里插入图片描述

观察者类

    // <h2>订阅(注册)则是被动,发布则是主动</h2>

        //使用立即函数
        var Observe =(function(){
            var message = {}
            return {
                //注册订阅信息模块
                //注册就是订阅者去注册信息!!这是被动的
                register:function(type,fn){
                    //检测有没有这个类型的消息队列
                    //不存在就创建一个该消息类型
                    if (typeof message[type] === 'undefined'){
                        message[type] = [fn]
                    }else{
                        message[type].push(fn)
                    }
                },
                //发布订阅信息模块
                //发布就是观察者去发布信息
                fire:function(type,args){
                    //判断是否有,就遍历,没有就返回
                    if (!message[type]) return
                    //要发布消息就要有发布信息对象,其中包括消息类型和携带消息
                    events = {
                        type:type,
                        args:args
                    }
                    //这里是指消息对应的动作序列的长度,不是message.length
                    for(let i = 0;i<message[type].length;i++){
                      message[type][i].call(this,events)
                    }
                },
                //取消订阅信息模块
                remove:function(type,fn){
                    //需要判断消息队列和动作
                    //判断是否是消息队列,也就是数组,好比二维数组
                    if(message[type] instanceof Array){
                        
                        //从最后一个消息进行遍历
                        for(let i = message[type].length;i>=0;i--){
                            //如果存在该动作!!!!
                          
                            if(message[type][i] == fn){
                                //splice,从i动作开始,删除一个
                                console.log('4')
                                message[type][i].splice(i,1)
                            }
                        }
                    }
                }
            }
        })()
        // //订阅消息->被动触发
        // Observe.register('test',function(e){
        //     console.log(e.args)
        // })
        // //发布消息->主动发布
        // Observe.fire('test',{fn:'happy1'})
        // Observe.fire('test',{fn:'happy2'})
        // Observe.fire('test',{fn:'happy3'})
        // // Observe.remove('test',function(e){
        // //     console.log(e.args)
        // // })
        // console.log(Observe)

例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src='./观察者模式.js'></script>
</head>
<body>
    <script>
        console.log(Observe)
        //学生类,是被动的,则是订阅/注册信息的
        //教师类,是主动的,则是发布信息的
        //实现了老师一个问题多个学生回答问题,学生回答的问题都给我push到这个消息队列
        //根据输出结果可以看出这是利用了队列,先进先出
        var Student = function(result){
            var self  = this
            self.result = result
            self.say = function(){
                console.log(self.result)
            }
        }
        Student.prototype.answer = function(question){
            //进行订阅,注册
            console.log('问题是'+question)
            Observe.register(question,this.say)

        }
        var Teacher = function(){}
        Teacher.prototype.ask = function(question){
            //进行发布
            Observe.fire(question)
            
        }
        var student1 = new Student('学生1回答了该问题')
        var student2 = new Student('学生2回答了该问题')
        var student3 = new Student('学生3回答了该问题')
        //学生进行回答问题
        student1.answer('什么是设计模式')
        student2.answer('什么是观察者模式')
        student3.answer('什么是建造者模式')

        //老师进行提问
        var teacher = new Teacher()
        teacher.ask('什么是设计模式')
        teacher.ask('什么是观察者模式')
        teacher.ask('什么是建造者模式')
        
    </script>
</body>
</html>

事件循环

详解JavaScript中的Event Loop(事件循环)机制

并发模型与事件循环

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值