5

MS: JS异步编程之Promise

 2 years ago
source link: https://lianpf.github.io/posts/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/05.js%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E4%B9%8Bpromise/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、Promise的使用

创建一个promise实例,方便处理异步操作

var p = new Promise(function(resolve,reject){
    setTimeout(function(){
       resolve("success")
    },1000);
    console.log("创建一个新的promise");
})
p.then(function(x){
  console.log(x)
})

// 输出:创建一个新的promise
// 输出:success

promise.then()链式调用

var p = new Promise(function(resolve, reject) {
  resolve()
});
p.then(...).then(...).then(...)

其他方法:

  • Promise.resolve()
  • Promise.all()
  • Promise.race()

二、Promise原理剖析

1.Promise 凭借什么消灭了回调地狱

2.为什么Promise要引入微任务

3.Promise 如何实现链式调用

正文。。。


三、手写Promise/A+规范的Promise

Promise/A+规范:
『术语』:

  • promise是一个对象或者函数,该对象或者函数有一个then方法
  • thenable是一个对象或者函数,用来定义then方法
  • valuepromise状态成功时的值
  • reasonpromise状态失败时的值
  • promise必须有3个状态,pendingfulfilled(resolved)rejected
    • 当处于pending状态的时候,可以转移到fulfilled(resolved)rejected状态
    • 当处于fulfilled(resolved)状态或rejected状态的时候,则不可变
  • 一个promise必须有一个then方法
    • then方法接受两个参数:promise.then(onFulfilled, onRejected)
      • onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法
      • onRejected表示状态从pending——>rejected所执行的方法。
  • 为了实现链式调用,then方法必须返回一个promise。promise2=promise1.then(onFulfilled,onRejected)

1.Promise基础结构

  • 执行回调数组:当resolve()延迟执行时,Promise的状态为pending,不应立即执行成功调用的函数,需要把它存起来,直到执行resolve再执行成功调用的函数
  • 链式调用:Promise里的链接调用是返回一个新的Promise对象
  • 异步执行:用setTimeout模拟微任务(JS事件循环执行顺序上和原生Promise有区别),把回调放入,等待确保异步执行

基础Promise

const isFunction = variable => typeof variable === 'function'

// 定义三个常量表示状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled',
const REJECTED = 'rejected'

class MyPromise {
  constructor(func) {
    this.onFulfilledArray = [] // 成功回调数组
    this.onRejectedArray = [] // 失败回调数组
    this.value = void 0
    this.reason = void 0
    this.status = PENDING
    func(this.resolve.bind(this), this.reject.bind(this))
  }
  resolve(val) {
    if (this.status !== PENDING) return
    const self = this
    const doResolve = () => {
      if (self.status === PENDING) {
        self.status = FULFILLED
        self.value = val
        // resolve里面将所有成功的回调拿出来执行
        while (self.onFulfilledArray.length) {
          const fn = self.onFulfilledArray.shift()
          fn()
        }
      }
    }
    setTimeout(doResolve(), 0)
  }
  reject(err) {
    if (this.status !== PENDING) return
    const self = this
    const doReject = () => {
      if (self.status === PENDING) {
        self.status = REJECTED
        self.reason = err
        // resolve里面将所有失败的回调拿出来执行
        self.onRejectedArray.forEach(fn => {
          fn()
        })
      }
    }
    setTimeout(doReject, 0)
  }
  then(onFulfilled, onRejected) {
    let nextPromise = void 0
    switch (this.status) {
      case PENDING:
        nextPromise = new MyPromise((resolve, reject) => {
          this.onFulfilledArray.push(() => {
            try {
              let res = onFulfilled(self.value)
              resolve(res)
            } catch (error) {
              reject(error)
            }
          })
          this.onRejectedArray.push(() => {
            try {
              let res = onRejected(self.reason)
              resolve(res)
            } catch (e) {
              reject(e)
            }
          })
        })
        break
      case FULFILLED:
        nextPromise = new MyPromise((resolve, reject) => {
          try {
            let res = onFulfilled(self.value)
            resolve(res)
          } catch (e) {
            reject(e)
          }
        })
        break
      case REJECTED:
        nextPromise = new MyPromise((resolve, reject) => {
          try {
            let res = onRejected(self.reason)
            resolve(res)
          } catch (e) {
            reject(e)
          }
        })
        onRejected(self.reason)
        break
    }
    return nextPromise
  }
  catch(err) {
    // 默认没有成功,只有失败
    return this.then(void 0, err)
  }
}

2.链式调用进阶版

以上的Promise存在一个问题,如果链式调用中Promise返回的是普通值,应该把值包装成新的Promise对象

  • 每个then方法都返回一个新的Promise对象(重点)
  • 如果then方法返回了一个Promise对象,则需要查看它的状态
    • 如果状态是成功,则调用resolve方法,把成功的状态传递给它;
    • 如果是失败的,则把失败的状态传递给下一个Promise对象
  • 如果then方法中返回的是一个原始数据类型值(如 Number、String 等)就使用此值包装成一个新的Promise对象返回
  • 如果then方法中没有return语句,则返回一个用undefined包装的Promise对象
  • 如果 then 方法没有传入任何回调,则继续向下传递(值的传递特性)
  • 如果是循环引用则需要抛出错误

修改then方法,增加resolvePromise函数

...
function resolvePromise(x, nextPromise, resolve, reject) {
  // 处理三种情况:
  // 1.循环引用
  // 2.x 为MyPromise
  // 3.x为基础类型
  if (x === nextPromise) {
    // x 和 nextPromise 指向同一对象,循环引用抛出错误。防止死循环
    return reject(new TypeError('循环引用'))
  } else if (x && (typeof x === 'object' || isFunction(x))) {
    // x 是对象或者函数
    let called = false // 避免多次调用
    try {
      let then = x.then // 判断对象是否有 then 方法
      if (isFunction(then)) {
        // then 是函数,就断定 x 是一个 MyPromise(根据Promise A+规范)
        then.call(
          x,
          function(y) {
            // 调用返回的MyPromise,用它的结果作为下一次then的结果
            if (called) return
            called = true
            resolvePromise(y, nextPromise, resolve, reject) // 递归解析成功后的值,直到它是一个普通值为止
          },
          function(r) {
            if (called) return
            called = true
            reject(r) // 取then时发生错误了
          }
        )
      } else {
        resolve(x) // 此时,x是一个普通对象
      }
    } catch (e) {
      reject(e)
    }
  } else {
    // x 是原始数据类型 / 没有返回值,这里即是undefined
    resolve(x)
  }
}
class MyPromise {
  constructor(func) {
    ...
  }
  ...
  then(onFulfilled, onRejected) {
    const self = this
    // 如果onFulfilled不是函数,给一个默认函数,返回value
    let realOnFulfilled = onFulfilled
    if (!isFunction(realOnFulfilled)) realOnFulfilled = value => value
    let realOnRejected = onRejected
    if (!isFunction(onRejected)) {
      realOnRejected = reason => {
        if (reason instanceof Error) {
          throw reason
        } else {
          throw new Error(reason)
        }
      }
    }
    let nextPromise = void 0
    switch (this.status) {
      case PENDING:
        nextPromise = new MyPromise((resolve, reject) => {
          this.onFulfilledArray.push(() => {
            try {
              let res = realOnFulfilled(self.value)
              resolvePromise(res, nextPromise, resolve, reject)
              // resolve(res)
            } catch (error) {
              reject(error)
            }
            // onFulfilled(self.value)
          })
          this.onRejectedArray.push(() => {
            try {
              let res = realOnRejected(self.reason)
              resolve(res)
            } catch (e) {
              reject(e)
            }
            // onRejected(self.reason)
          })
        })
        break
      case FULFILLED:
        nextPromise = new MyPromise((resolve, reject) => {
          try {
            let res = realOnFulfilled(self.value)
            resolvePromise(res, nextPromise, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        // onFulfilled(self.value)
        break
      case REJECTED:
        nextPromise = new MyPromise((resolve, reject) => {
          try {
            let res = realOnRejected(self.reason)
            resolvePromise(res, nextPromise, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        onRejected(self.reason)
        break
    }
    return nextPromise
  }
  ...
}

3.Promise.prototype.finally实现

有两大重点:

  • 无论当前这个 Promise 对象最终的状态是成功还是失败 ,finally 方法里的回调函数都会执行一次
  • 在 finally 方法后面可以继续链式调用 then 方法,拿到当前这个 Promise 对象最终返回的结果

挂载在MyPromise.property原型上,或者类似catch实现:

MyPromise.prototype.finally = function(callback) {
  return this.then(
    data => {
      return MyPromise.resolve(callback()).then(() => data)
    },
    err => {
      return MyPromise.resolve(
        callback().then(() => {
          throw err
        })
      )
    }
  )
}

4.Promise.all()Promise.race()实现

Promise.all():可将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值

...
function isPromise(val) {
  return val && typeof val.then === 'function'
}
class MyPromise() {
  ...
}
...
MyPromise.all = function(promises) {
  if (!Array.isArray(promises)) {
    throw new Error('Not a array')
  }
  return new MyPromise((resolve, reject) => {
    let result = []
    let times = 0 // 计数器
    function processData(index, val) {
      result[index] = val
      if (++times === promises.length) {
        resolve(result)
      }
    }
    for (let i = 0; i < promises.length; i++) {
      let p = promises[i]
      if (isPromise(p)) {
        // MyPromise对象
        p.then(data => {
          processData(i, data)
        }, reject)
      } else {
        processData(i, p) // 普通值
      }
    }
  })
}

Promise.race()在执行多个异步操作中,不管结果是成功状态还是失败状态,只保留取第一个执行完成的异步操作的结果。其他的方法仍会执行,但结果会被抛弃

...
class MyPromise() {
  ...
}
...
MyPromise.race = function(promises) {
  if (!Array.isArray(promises)) {
    throw new Error('Not a array')
  }
  return new MyPromise((resolve, reject) => {
    if (promises.length === 0) {
      return
    } else {
      for (let p of promises) {
        p.then(
          value => {
            resolve(value)
          },
          reason => {
            reject(reason)
          }
        )
      }
    }
  })
}

5.最终手写版源码

代码太长,直接参考源码仓库 examples | lianpf.github。欢迎star


思路很清晰的两篇文章:


最后, 希望大家早日实现:成为前端高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!


Recommend

  • 179
    • 掘金 juejin.im 6 years ago
    • Cache

    Promise 异步流程控制

    前言 最近部门在招前端,作为部门唯一的前端,面试了不少应聘的同学,面试中有一个涉及 Promise 的一个问题是: 网页中预加载20张图片资源,分步加载,一次加载10张,两次完成,怎么控制图片请求的并发,怎样感知当前异步请求是否已完成? 然而能全部答上

  • 89

    日常开发过程中,时不时会遇到要同时预加载几张图片,并且等都加载完再干活的情况,结合 Promise 和 async/await 代码会优雅很多,但也容易遇到坑,今天就来简单聊聊。 ES5 先从最基本的 ES5 说起,基本思路就是做一个计数器,每次 image

  • 53
    • 掘金 juejin.im 6 years ago
    • Cache

    异步解决方案----Promise与Await

    前言 异步编程模式在前端开发过程中,显得越来越重要。从最开始的XHR到封装后的Ajax都在试图解决异步编程过程中的问题。随着ES6新标准的到来,处理异步数据流又有了新的方案。我们都知道,在传统的ajax请求中,当异步请求之间的数据存在依赖关系的时候,就可能产生...

  • 18
    • www.jianshu.com 4 years ago
    • Cache

    异步编程二:promise模式 - 简书

    异步编程二:promise模式青_雉0.1922020.01.18 22:51:51字数 1,656阅读 1,080书接上回,我们聊了为什么要有异步编程模式以及异步编程...

  • 3

    其实在 ES6 标准出现之前,社区就最早提出了 Promise 的方案,后随着 ES6 将其加入进去,才统一了其用法,并提供了原生的 Promise 对象。 Promise 的基本情况 如果一定要解释 Promise 到底是什么,简单来说它...

  • 4

    学习 Promise,掌握未来世界 JS 异步编程基础发表于2018-06-04更新于2020-03-12字数统计3.2k阅读时长24分 其实想写 Promise 的使用已经很长时间了。一个是在实际编码的过程中经常用到,一个是确实有时候小伙伴们在使用时也会遇...

  • 2

    一文看懂JS异步编程,回调、Promise、Generator、async/await用法详解 | Lenix Blog JavaScript是一种单线程的编程语言,需要通过异步的方式才能获得较高的性能...

  • 4

    Promise 一种更优的异步编程统一 方法,如果直接使用传统的回调函数去完成复杂操作就会形成回调深渊 // 回调深渊 $.get('/url1'() => { $.get('/url2'() => { $.get('/url3'() => { $.get('/url4'() =>...

  • 4
    • www.fly63.com 8 months ago
    • Cache

    Promise 与异步编程

    Promise 是 JavaScript 中的一个重要概念,与前端的工作更是息息相关。因此本文将整理一下 Promise 在日常工作中的应用。从 MDN | 使用 Promise 中我们能学习到 Promise 的基础使用与错误处理、...

  • 1

    Promise, async, await实现异步编程,代码详解

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK