Junwen's home
  • ES6

    • ES6 Decorator
    • ES6核心特性
    • Promise&Generator
  • js原理

    • 简单实现bind、apply和call
    • 如何遍历一个dom tree
    • 实现函数currying
    • 实现一个event
    • 详解js的继承
    • 详解requestAnimationFrame
    • Canvas api详解
    • DOM事件
    • EventLoop详解
    • JavaScript的内存管理
    • JavaScript的运行机制
    • Math对象
    • new操作符都做了什么
    • create基本实现原理
    • Set、Map、WeakSet和WeakMap
    • web worker原理
    • WebGL教程(MDN)
  • jsInfoSeries

    • 简介
    • JavaScript基础知识
    • 基础知识2
    • 基础知识3
    • 基础知识4
  • 技巧

    • 5个js解构有趣用途
    • 如何使用set提高代码性能
    • cordova构建项目时的问题
    • js中轻松遍历对象属性的几种方式
  • 怎么写出更好的css
  • BFC详解
  • box-shadow详解
  • CSS小技巧
  • Grid布局详解
HTML
  • IP十问
  • http笔试
  • http协议
  • 浏览器原理
  • 浏览器缓存其实就这么一回事儿
  • 浏览器兼容性问题
  • 移动端开发兼容性适配
  • 前端性能优化
  • 前端如何进行seo优化
  • webpack

    • webpack HMR
    • webpack优化基本方法
  • leetcode题解

    • 两数之和
    • 判断整数是否为回文串
    • 无重复字符的最长子串
  • Js链表
  • JavaScript排序
  • React

    • 虚拟DOM原理理解
    • React Hook
    • 组件复用指南
  • Vue

    • Vue举一反三
面试题
读书笔记
GitHub (opens new window)

Syun0216

多读书多种树
  • ES6

    • ES6 Decorator
    • ES6核心特性
    • Promise&Generator
  • js原理

    • 简单实现bind、apply和call
    • 如何遍历一个dom tree
    • 实现函数currying
    • 实现一个event
    • 详解js的继承
    • 详解requestAnimationFrame
    • Canvas api详解
    • DOM事件
    • EventLoop详解
    • JavaScript的内存管理
    • JavaScript的运行机制
    • Math对象
    • new操作符都做了什么
    • create基本实现原理
    • Set、Map、WeakSet和WeakMap
    • web worker原理
    • WebGL教程(MDN)
  • jsInfoSeries

    • 简介
    • JavaScript基础知识
    • 基础知识2
    • 基础知识3
    • 基础知识4
  • 技巧

    • 5个js解构有趣用途
    • 如何使用set提高代码性能
    • cordova构建项目时的问题
    • js中轻松遍历对象属性的几种方式
  • 怎么写出更好的css
  • BFC详解
  • box-shadow详解
  • CSS小技巧
  • Grid布局详解
HTML
  • IP十问
  • http笔试
  • http协议
  • 浏览器原理
  • 浏览器缓存其实就这么一回事儿
  • 浏览器兼容性问题
  • 移动端开发兼容性适配
  • 前端性能优化
  • 前端如何进行seo优化
  • webpack

    • webpack HMR
    • webpack优化基本方法
  • leetcode题解

    • 两数之和
    • 判断整数是否为回文串
    • 无重复字符的最长子串
  • Js链表
  • JavaScript排序
  • React

    • 虚拟DOM原理理解
    • React Hook
    • 组件复用指南
  • Vue

    • Vue举一反三
面试题
读书笔记
GitHub (opens new window)
  • ES6

    • ES6 Decorator
    • ES6核心特性
    • Promise&Generator
    • js原理

      • 简单实现bind、apply和call
      • 如何遍历一个dom tree
      • 实现函数currying
      • 实现一个event
      • 详解js的继承
      • 详解requestAnimationFrame
      • Canvas api详解
      • DOM事件
      • EventLoop详解
      • JavaScript的内存管理
      • JavaScript的运行机制
      • Math对象
      • new操作符都做了什么
      • Object.create基本实现原理
      • Set、Map、WeakSet和WeakMap
      • web worker原理
      • WebGL教程(MDN)
    • jsInfoSeries

      • 简介
      • JavaScript基础知识
      • 基础知识2
      • 基础知识3
      • 基础知识4
    • 技巧

      • 5个js解构有趣用途
      • 如何使用set提高代码性能
      • cordova构建项目时的问题
      • js中轻松遍历对象属性的几种方式
    • JavaScript
    junwen
    2019-08-06

    Promise&Generator

    Promise & Generator 简单入门

    参考Promise & Generator——幸福地用同步方法写异步 JavaScript (opens new window)

    Promise 的特性:

    1. 关于 promise,首先要意识到它是一种对象。这种对象可以用 Promise 构造函数来创建,也可以通过 Nodejs 本身一些默认的返回来获取这种对象。
    2. promise 对象有三种状态:Pending,Fulfilled,Rejected。分别对应着未开始的状态,成功的状态,以及失败的状态。
    3. 这种对象常常封装着异步的方法。在异步方法里面,通过 resolve 和 reject 来划定什么时候算是成功,什么时候算是错误,同时传参数给这两个函数。这些参数就是异步得到的结果或者错误。
    4. 异步有成功的时候,也有错误的时候。对象通过 then 和 catch 方法来规定异步结束之后的操作(正确处理函数/错误处理函数)。而 then 和 catch 是 Promise.prototype 上的函数,因此“实例化”之后(其实并非真正的实例)可以直接使用。
    5. 这个 promise 对象还有一个神奇的地方,就是可以级联。每一个 then 里面返回一个 promise 对象,就又像上面所提的那样,有异步就等待异步,然后选择出规定好的正确处理函数还是错误处理函数。

    # 构建 Promise

    function PromiseCustom(Fn) {
      let resolveCall = () => {
        console.log('default call')
      }
      this.then(onFullFill => {
        // 接受onFullFill方法
        resolveCall = onFullFill
      })
      function resolve(v) {
        resolveCall(v)
      }
      Fn(resolve)
    }
    
    new PromiseCustom((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 2000)
    }).then(r => {
      console.log(r)
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    在 new Promise 的时候会立即执行 fn,遇到异步方法,于是先执行 then 中的方法,将 onFullFill 存储到 resolveCall 中。异步时间到了以后,执行 resolve,从而执行 resolveCall 存储的 then 方法。

    这里会有一个问题,就是 Promise 接受的方法不是异步,就会使 resolve 比 then 方法先执行。而此时 resolveCall 并没有被赋值,得不到我们想要的结果,所以要给 resolve 加上异步操作,从而保证 then 先执行

    function resolve(v) {
      setTimeout(_ => {
        resolveCall(v)
      })
    }
    
    1
    2
    3
    4
    5

    # 增加链式调用

    在每个 then 方法中 return 一个 this

    function Promise(Fn) {
      this.resolves = [] // 方便存储onFullfilled
      this.then = onFullfilled => {
        this.resolves.push(onFullfilled)
        return this
      }
      let resolve = value => { // 箭头函数,不用担心this指针问题
        setTimeout(- => {
          this.resolves.forEach(fn => fn(value))
        })
      }
      Fn(resolve)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    可以看到,这里将接收 then 回调的方法改为了 Promise 的属性 resolves,而且是数组。这是因为如果有多个 then,依次 push 到数组的方式才能存储,否则后面的 then 会将之前保存的覆盖掉。这样等到 resolve 被调用的时候,依次执行 resolves 中的函数就可以了。这样可以进行简单的链式调用。

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

    但我们会有这样的需求,某一个 then 链想自己 return 一个参数供后面的 then 使用:

    eg:

    then(r => {
      console.log(r)
      return r
    }).then()
    
    1
    2
    3
    4

    要做到这一步,需要再加一个处理

    let resolve = value => {
      setTimeout(_ => {
        // 每次执行then的回调时判断一下是否有返回值,有的话更新value
        this.resolves.forEach(fn => (value = fn(value) || value))
      })
    }
    
    1
    2
    3
    4
    5
    6

    # 增加状态

    增加 pending,fullfilled 和 rejected 状态

    function Promise(Fn) {
      this.resolves = []
      this.status = 'PENDING' // 初始为'PENDING'状态
      this.value
      this.then = onFulfilled => {
        if (this.status === 'PENDING') {
          // 如果是'PENDING',则储存到数组中
          this.resolves.push(onFulfilled)
        } else if (this.status === 'FULFILLED') {
          // 如果是'FULFILLED',则立即执行回调
          console.log('isFULFILLED')
          onFulfilled(this.value)
        }
        return this
      }
    
      let resolve = value => {
        if (this.status === 'PENDING') {
          // 'PENDING' 状态才执行resolve操作
          setTimeout(_ => {
            //状态转换为FULFILLED
            //执行then时保存到resolves里的回调
            //如果回调有返回值,更新当前value
            this.status = 'FULFILLED'
            this.resolves.forEach(fn => (value = fn(value) || value))
            this.value = value
          })
        }
      }
    
      Fn(resolve)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    这里可能会有同学觉得困惑,我们通过一个例子来说明增加的这些处理到底有什么用。

    let getInfo = new Promise((resolve, reject) => {
      resolve('success')
    }).then(_ => {
      console.log('hahah')
    })
    
    setTimeout(_ => {
      getInfor.then(r => {
        console.log(r) // success
      })
    }, 200)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    在 resolve 函数中,判断了'PENDING' 状态才执行 setTimeout 方法,并且在执行时更改了状态为'FULFILLED'。这时,如果运行这个例子,只会输出一个‘success’,因为接下来的异步方法调用时状态已经被改为‘FULFILLED’,所以不会再次执行。

    这种情况要想它可以执行,就需要用到 then 方法里的判断,如果状态是'FULFILLED',则立即执行回调。此时的传参是在 resolve 执行时保存的 this.value。这样就符合 Promise 的状态原则,PENDING 不可逆,FULFILLED 和 REJECTED 不能相互转化。 * 增加失败处理

    onRejected:

    this.reason
    this.rejects = []
    
    // 接收失败的onRejected函数
    
    if (this.status === 'PENDING') {
      this.rejects.push(onRejected)
    }
    
    // 如果状态是'REJECTED',则立即执行onRejected。
    
    if (this.status === 'REJECTED') {
      onRejected(this.reason)
    }
    
    // reject方法
    
    let reject = reason => {
      if (this.status === 'PENDING') {
        setTimeout(_ => {
          //状态转换为REJECTED
          //执行then时保存到rejects里的回调
          //如果回调有返回值,更新当前reason
          this.status = 'REJECTED'
          this.rejects.forEach(fn => (reason = fn(reason) || reason))
          this.reason = reason
        })
      }
    }
    
    // 执行Fn出错直接reject
    
    try {
      Fn(resolve, reject)
    } catch (err) {
      reject(err)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    在执行储存 then 中的回调函数那一步有一个细节一直没有处理,那就是判断是否有 onFulfilled 或者 onRejected 方法,因为是允许不要其中一个的。现在如果 then 中缺少某个回调,会直接 push 进 undefined,如果执行的话就会出错,所以要先判断一下是否是函数。

    this.then = (onFulfilled, onRejected) => {
      // 判断是否是函数,是函数则执行
    
      function success(value) {
        return (typeof onFulfilled === 'function' && onFulfilled(value)) || value
      }
    
      function erro(reason) {
        return (typeof onRejected === 'function' && onRejected(reason)) || reason
      }
    
      // 下面的处理也要换成新定义的函数
    
      if (this.status === 'PENDING') {
        this.resolves.push(success)
        this.rejects.push(erro)
      } else if (this.status === 'FULFILLED') {
        success(this.value)
      } else if (this.status === 'REJECTED') {
        erro(this.reason)
      }
      return this
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    因为 reject 回调执行时和 resolve 基本一样,所以稍微优化一下部分代码。

    if (this.status === 'PENDING') {
      let transition = (status, val) => {
        setTimeout(_ => {
          this.status = status
          let f = status === 'FULFILLED',
            queue = this[f ? 'resolves' : 'rejects']
          queue.forEach(fn => (val = fn(val) || val))
          this[f ? 'value' : 'reason'] = val
        })
      }
    
      function resolve(value) {
        transition('FULFILLED', value)
      }
    
      function reject(reason) {
        transition('REJECTED', reason)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 串行 Promise

    假设有多个 ajax 请求串联调用,下一个需要上一个的返回值作为参数,并且要 return 一个新的 Promise 捕捉错误。之前的例子可以执行是因为没有在 then 中做异常处理,即没有 reject,只是传递了数据。我们需要每个 then 方法中 return 一个新的 Promise。

    // 把then方法放到原型上,这样new一个新的Promise时就回去引用prototype的then方法,而不是复制一份
    Promise.prototype.then = function(onFulfilled, onRejected) {
      let promise = this
      return new Promise((resolve, reject) => {
        function success(value) {
          let val =
            (typeof onFulfilled === 'function' && onFulfilled(value)) || value
          resolve(val) // 执行完这个then方法的onFulfilled以后,resolve下一个then方法
        }
    
        function erro(reson) {
          let rea = (typeof onRejected === 'function' && onRejected(reson)) || reson
          reject(rea) // 同resolve
        }
        if (promise.status === 'PENDING') {
          promise.resolves.push(success)
          promise.rejects.push(erro)
        } else if (promise.status === 'FULFILLED') {
          success(promise.value)
        } else if (promise.status === 'REJECTED') {
          erro(promise.reason)
        }
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    在成功的函数中还需要做一个处理,用以支持在 then 的回调函数(onFulfilled)中 return 的 Promise。如果 onFulfilled 方法 return 的是一个 Promise,则直接执行它的 then 方法。如果成功了,就继续执行后面的 then 链,失败了直接调用 reject。

    function success(value) {
      let val = tyoeof onFulfilled === 'function' && onFulfilled(value) || value;
      if(val && typeof val['then'] === 'function') { // 判断是否有then方法
        val.then(function(value) { // 如果返回的是Promise 则直接执行得到结果后在调用后面的then方法
          resolve(value),
        }, function(reson) {
          reject(reson)
        })
      } else {
        resolve(val)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    找个例子测试一下

    function getInfo(success, fail) {
      return new Promise((resolve, reject) => {
        setTimeout(_ => {
          let ran = Math.random()
          console.log(success, ran)
          if (ran > 0.5) {
            resolve(success)
          } else {
            reject(fail)
          }
        }, 200)
      })
    }
    getInfo('Vchat', 'fail')
      .then(
        res => {
          console.log(res)
          return getInfo('可以线上预览了', 'erro')
        },
        rej => {
          console.log(rej)
        }
      )
      .then(
        res => {
          console.log(res)
        },
        rej => {
          console.log(rej)
        }
      )
    // 输出
    // Vchat 0.8914818954810422
    // Vchat
    // 可以线上预览了 0.03702367800412443
    // erro
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    Generator 的特性:

    1. 函数的内部通过 yield 来推进函数。通过定义 yield 后面的值来决定返回的 value。
    2. 数返回一个遍历器,这个遍历器有一个 next 方法,可以获取一个对象,这个对象就包含了 yield 定义好的参数。

    # 如何用同步的写法写异步的代码

    function* foo(res, name, newPassword, oldPassword) {
      try {
        // yield一个promise对象,如果有错误就会被后面的catch捕捉到,成功就会返回user。
        const user = yield new Promise(function(resolve, reject) {
          // 常见的数据库读取星系
          User.get(name, function(err, user) {
            if (err) reject(err)
            resolve(user)
          })
        })
    
        if (user.password != oldPassword) {
          return res.send({ errorMsg: '密码输入错误!' })
        }
    
        // 看到这一个异步函数和上一个的异步在写法上是基本上“同步”的,没有了相互嵌套,很优雅~也更加方便了debug~
        yield new Promise(function(resolve, reject) {
          User.update(name, newPassword, function(err) {
            if (err) reject(err)
            res.send({ msg: '你成功更换密码了!' })
            resolve()
          })
        })
      } catch (e) {
        console.log('Error:', e)
        return res.send({ errorMsg: 'Setting Fail!' })
      }
    }
    
    // 使用的话就直接调用co包含对应的Generator函数即可。
    co(foo(res, name, newPassword, oldPassword))
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    关于co (opens new window)

    在Github上编辑此页 (opens new window)
    #ES6#JavaScript
    上次更新: 3/22/2021, 3:47:15 AM
    ES6核心特性
    简单实现bind、apply和call

    ← ES6核心特性 简单实现bind、apply和call→

    最近更新
    01
    如何打造全链路项目生命周期的统一交付平台
    04-10
    02
    如何建立前端标准化研发流程
    04-10
    03
    如何从0到1一步步成体系地搭建CI
    04-10
    更多文章>
    Theme by Vdoing | Copyright © 2019-2021 Syun
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式