Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >从实现一个Promise说起

从实现一个Promise说起

作者头像
MrTreasure
发布于 2018-06-19 02:54:11
发布于 2018-06-19 02:54:11
43700
代码可运行
举报
文章被收录于专栏:不止是前端不止是前端
运行总次数:0
代码可运行

前言

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const p = new Promise((resolve, reject) => {
  console.log('A')
  setTimeout(() => {
    console.log('B')
    resolve('C')
  })
})

p.then(res => {
  console.log(res)
})

// A B C D

尽管工作中用了无数次Promise async await,但是在写下这篇文章之前,却不知道Promise背后发生了些什么,我一直以为的逻辑是先等待Promise构造方法中的异步函数完成后,再调用then方法执行其中的函数。然而事情并没有这么简单,这篇文章将以深入浅出的方式理解Promise背后究竟发生了什么

构造一个Promise

按照Promise/A+规范,一个Promise应该包含以下数据结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface IPromise {
  status: STATUS // 表明当前Promise的状态,不可逆,在进行then添加方法时,会根据这个状态做出不同的处理
  value: any // 异步函数执行成功后返回的值
  reason: any // 异步函数执行失败后返回的值
  onResolvedCallbacks: Function[] // 保存then方法添加的成功后执行函数
  onRejectCallbacks: Function[] // 保存then方法添加的失败后的执行函数
}

enum STATUS {
  PENDING,
  FULFILLED,
  REJECTED
}

接着动手实现一个Promise,因为在TS环境中Promise已经有了,为了避免和已有的冲突,我把自己构造的对象命名为MyPromise

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyPromise {
  private status: STATUS
  private value: any
  private reason: any
  private onResolvedCallBacks: Function [] = []
  private onRejectCallBacks: Function [] = []

  constructor(executor: Function) {
    const self = this
    function resolve(value: any) {
      // 改变当前Promise的状态
      if (p.status === STATUS.PENDING) {
        p.stats = STATUS.FULFILLED
        p.value = value
        p.onResolveCallbacks.forEach(fun => {
          fun(p.value)
        })
      }
    }
    function reject(reason: any) {
      if (p.status === STATUS.PENDING) {
        p.status = STATUS.REJECTED
        p.reason = reason
        p.onRejectCallbacks.forEach(fun => fun(reason))
      }
    }
  }

  public then(onFulfilled: Function, onReject?: Function) {
    this.onResolveCallbacks.push(onFulfilled)
    if (onReject) {
      this.onRjectCallback.push(onRject)
      return
    }
    return this
  }
}

这是一个最最基本的Promise实现,写到这里我们试着从代码中了解下Promise究竟干了些什么。 我们知道JS是异步非阻塞单线程的语言,遇到异步任务时,将会向事件队列添加一个函数,直到异步任务完成时,线程再执行这个函数,基于此,在JS中很多地方用到了订阅者模式。

Promise正好是一个订阅者模式的实现executor就是我们添加的订阅的数据源,我们向这个源注册了两个钩子resolve, reject,分别在异步事件的成功和失败时执行,相当于订阅者的notify方法。

then方法则是向订阅者注册事件。这样就能初步理解Promise干了什么。

resolve,reject方法的改进

按照Promise预期的设计,then方法时同步的向Promise的待处理队列添加函数,而executor函数则是异步的执行一个函数,再调用其中的resolve或者reject方法,也就是说then一定先于executor执行。上面的代码中如果executor是一个同步的方法,那么新建这个MyPromise实例时,resolve就已经被调用了,导致then添加的方法无法执行。所以我们需要做出一定的处理,保证resolve之前,已经注册了事件处理函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function reject (value: any) {
  // 调用setImmediate方法,保证resolve一定会在通过then同步的注册的方法后调用
  // setImmediate将会把回调中的函数加入到下一个task,优先级要比setTimeout高
  // JS中的Promise.resolve方法时将回调中的函数加入到当前的microtask队列,优先级要比前者高
  setImmediate(() => {
    if (p.status === STATUS.PENDING) {
      p.stats = STATUS.FULFILLED
      p.value = value
      p.onResolveCallbacks.forEach(fun => {
        fun(p.value)
      })
    }
  })
}

then方法中添加Promise的链式调用

之前的MyPromise通过then方法注册事件后,虽然返回了this能够进行链式调用,但是如果注册的事件返回的是Promise,包含异步的事件则会出错。针对这种状况我们需要进行特殊的处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public then (onFulfilled: Function, onReject?: Function) {
  // 包装一个Promise
  let promise2: MyPromise
  // 保存当前this——外部的Promise
  const self: MyPromise = this
  // 如果当前异步函数执行成功,得到了值
  if (this.status === STATUS.FULFILLED) {
    // 声明一个新的 promise2
    promise2 = new MyPromise((resovle, reject) => {
      setImmediate(() => {
        try {
          // then添加的方法返回一个promise
          let res = onFulfilled(self.value)
          resolvePromise(promise2, res, resovle, reject)
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  if (this.status === STATUS.PENDING) {
    promise2 = new MyPromise((reslove, reject) => {
      self.onResolveCallbacks.push(value => {
        try {
          let res = onFulfilled(value)
          resolvePromise(promise2, res, reslove, reject)
        } catch (error) {
          reject(error)
        }
      })
      self.onRejectCallbacks.push(reason => {
        try {
          let res = onReject(reason)
          resolvePromise(promise2, res, reslove, reject)
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  function resolvePromise (promise: MyPromise, res: any, resolve: Function, reject: Function) {
    // 暂时不清楚什么情况下会出现循环应用
    if (promise === res) {
      return reject(new TypeError('循环引用'))
    }
    let then
    let called
    
    // 如果onFulfilled方法返回的res不为空,并且可能是object(可能是一个Promise或者一般对象)或者function
    if (res !== null && ((typeof res === 'object' || typeof res === 'function'))) {
      try {
        then = res.then
        // 如果res具有then属性,并且是一个function,说明可能是一个Promise(这里应该用类型判断)
        if (typeof then === 'function') {
          // 重复调用then方法, 直到res不再是一个Promise
          then.call(res, function (res2) {
            // 每次调用then方法都会返回一个新的Promise,如果当前的Promise已经注册过事件了,将会直接return
            if (called) return
            called = true
            resolvePromise(promise, res2, resolve, reject)
          }, function (err) {
            if (called) return
            called = true
            reject(err)
          })
        } else {
          // 调用resolve
          resolve(res)
        }
      } catch (error) {
        if (called) return
        called = true
        reject(error)
      }
    } else {
      resolve(res)
    }
  }
  
  return promise2
}

改进后的then方法改进了两个地方

  1. 判断通过then方法注册事件时Promise的状态,一个Promise的状态应该是确定的不可逆的,即只能从PENDING状态转换为fulfilled或者reject,当调用then方法注册事件时,如果此时这个Promise已经不是PENDING了,将会根据现在的Promise类型执行then注册的函数
  2. 每次调用then方法进行函数注册的时候都会返回一个新的Promise,这个Promise保证了then的链式调用。接着我们考虑了注册的onFulfilled函数,如果这个函数返回的是一个Promise,则继续向它注册事件

小结

  1. Promise本质就是一个发布订阅模式,异步函数是整个模型的驱动器,完成时调用resolve执行成功方法,then是向该模型注册事件
  2. Promise巧妙的利用发布订阅模式,将异步事件的发生与发生之后的执行解耦了,通过resolve钩子触发注册的函数,使得我们的关注点在then之后的方法
  3. Typescript用起来真是爽

这篇文章只是简单的介绍了Promise背后执行的原理,还有Promise.all Promise.race方法没有实现,不过已经不重要了,我们只需要记得Promise是一个发布订阅模式就OK,generator和 async await的方法也没有实现。不过基于此,可以大胆的猜测。通过await执行的Promise,是将原本resolve我们注册的函数改为了执行await方法中的函数,再把值取出来给我们调用。大抵应该是这个原理。实现上需要写一个generator runtime这也超过大部分人的能力。因此能够用好async await就好了。

本文的源代码在 Github 欢迎star

本文参考自文章 确认过眼神,你就是我的Promise~~

以上都是我瞎编的

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.05.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
手写系列-这一次,彻底搞懂 Promise
想要实现 Promise,必须先了解 Promise 是什么,以及 Promise 有哪些功能。
游魂
2023/10/17
2520
手写系列-这一次,彻底搞懂 Promise
手写一个Promise/A+,完美通过官方872个测试用例
前段时间我用两篇文章深入讲解了异步的概念和Event Loop的底层原理,然后还讲了一种自己实现异步的发布订阅模式:
蒋鹏飞
2020/10/15
7480
手写一个Promise/A+,完美通过官方872个测试用例
从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节
这道面试题是无意间在微信群里看到的,据说是某厂的面试题。一般关于 Promise 的面试题无非是考察宏微任务、EventLoop 之类的,当我认真去分析这道题的时候,越看越不对劲,感觉有诈!这是要考察啥?
ConardLi
2021/04/23
1.4K0
深入理解 Promise 之手把手教你写一版
什么是 Promise? 语法上:Promise 是一个构造函数,返回一个带有状态的对象 功能上:Promise 用于解决异步函数并根据结果做出不同的应对 规范上:Promise 是一个拥有 then 方法的对象(在 JS 里函数也是对象) 为什么要用 Promise? 前端最令人头疼的事情之一就是处理异步请求: function load() { $.ajax({ url: 'xxx.com', data: 'jsonp', success: fun
用户1097444
2022/06/29
5200
深入理解 Promise 之手把手教你写一版
手撕代码之Promise的实现
通过查看 Promise 我们可以发现 resolve,reject,all,race是静态方法,then,catch,finally是实例方法。
前端小tips
2021/12/06
4830
手撕代码之Promise的实现
从0到1实现Promise前言正文结束
Promise大家一定都不陌生了,JavaScript异步流程从最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果对于这些不熟悉的可以参考我另一篇文章《JavaScript异步编程》),这不仅仅是技术实现的发展,更是思想上对于如何控制异步的递进。Promise作为后续方案的基础,是重中之重,也是面试时候最常被问到的。
leocoder
2018/10/31
1K0
一步一步手写完美符合PromiseA+规范的Promise
Promise作为异步编程的一种解决方案,已经变得十分常用。而手写Promise也是面试中的高频题,今天我们就来一步一步完成一个完美符合PromiseA+规范的Promise吧
coder_koala
2021/04/21
4390
Promise实现原理
我们工作中免不了运用promise用来解决异步回调问题。平时用的很多库或者插件都运用了promise 例如axios、fetch等等。但是你知道promise是咋写出来的呢?
OECOM
2020/07/01
1.1K0
JS 原生方法原理探究(十):如何手写实现 Promise/A+ 及相关方法?
Promise 构造函数的作用是创建一个 promise 实例。对于一个 promise 实例来说,它会有几个基本的属性:status 记录 promise 的状态(初始为 pending),value 记录 promise resolve 的值(初始为 null),reason 记录 promise reject 的值(初始为 null)。
Chor
2021/09/08
7750
前端二面手写面试题总结
then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。
helloworld1024
2022/10/27
8230
手动实现Promise/A+
promise 如今已经深度融入前端开发技术当中,很多模块内部都依赖 promise,使用 promise 可以很好的解决异步回调问题。
多云转晴
2020/08/13
4930
js手写前端需要掌握的点
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
helloworld1024
2022/10/24
1.9K0
图解JavaScript——代码实现【2】(重点是Promise、Async、发布/订阅原理实现)
本节主要阐述六种异步方案:回调函数、事件监听、发布/订阅、Promise、Generator和Async。其中重点是发布/订阅、Promise、Async的原理实现,通过对这几点的了解,希望我们前端切
前端迷
2020/10/26
7500
图解JavaScript——代码实现【2】(重点是Promise、Async、发布/订阅原理实现)
Promise杂记 前言APIPromise特点状态追随V8中的async await和Promise实现一个Promise参考
作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇如何实现更快的async await,借着这个机会整理了Promise的相关理解。文中如有错误,请轻喷~
菜的黑人牙膏
2019/01/21
1.1K0
如何写出一个惊艳面试官的 Promise【近 1W字】 前言源码1.Promise2.Generator3.async 和 await4.Pro
1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码); 2.在写之前我们简单回顾下他们的作用; 3.手写模块见PolyFill.
火狼1
2020/05/09
7060
如何写出一个惊艳面试官的 Promise【近 1W字】
                            前言源码1.Promise2.Generator3.async 和 await4.Pro
从零开始实现一个Promise
众所周知,Promise是ES6引入的新特性,旨在解决回调地狱。下面是一个简单的例子:控制接口调用顺序:
helloworld1024
2022/10/14
1760
手写Promise,理解内部原理
1class myPromise { 2 // 为了统一用static创建静态属性,用来管理状态 3 static PENDING = "pending"; 4 static FULFILLED = "fulfilled"; 5 static REJECTED = "rejected"; 6 7 // 构造函数:通过new命令生成对象实例时,自动调用类的构造函数 8 constructor(func) { 9
心念
2023/01/11
2540
手写Promise完整介绍
Promise是一种用于处理异步操作的机制,它可以将异步操作的结果以同步的方式进行处理和返回。在JavaScript中,Promise是一种内置对象,但我们也可以手动实现一个Promise类来更好地理解其原理和工作方式。
can4hou6joeng4
2023/11/16
4390
社招前端经典手写面试题合集
使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
helloworld1024
2022/10/19
7500
JS:你真的会用 Promise 吗?
试想一下,有 3 个异步请求,第二个需要依赖第一个请求的返回结果,第三个需要依赖第二个请求的返回结果,一般怎么做?
WEBJ2EE
2019/07/19
2.6K0
JS:你真的会用 Promise 吗?
推荐阅读
相关推荐
手写系列-这一次,彻底搞懂 Promise
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验