【手写 Promise 源码】第七篇 - 实现 Promise 返回值 x 的处理

news2024/11/19 1:46:36

一,前言

上篇,实现了 Promise 的链式调用功能,主要涉及到以下几个点:

  • 介绍了 Promise 的链式调用,返回普通值和抛出异常的共5种情况;
  • 分析了当前 Promise 源码的问题以及解决方案;
  • Promise 链式调用的实现、功能测试、执行过程分析;

Promise A+ 规范中提到了对 then 中方法返回值 x 的处理,核心方法:resolvePromise

本篇,将实现 Promise 对返回值 x 的处理;


二,Promise A+ 规范

上一篇,在实现了 Promsie 链式调用的过程中,介绍了返回值 x 为普通值和抛出异常的情况;

返回值 x 还有可能是 Promise 类型,根据 Promise 状态决定执行成功/失败处理;

下面是 Promise A+ 规范中,与 “Promise 解析过程”有关的内容;

1,规范

Promise A+ 规范相关内容:

image.png

2,翻译

  • Promise 的解析过程,是以一个 promise 和 一个值 做为参数的抽象过程,可以表示为[[Resolve]](promise, x).如果 x 是一个 thenable,它试图使 promise 采用 x 状态,假设 x 的行为至少有点像 promise.否则,使用 x 的值执行 promise.

  • 这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

  • 运行 [[Resolve]](promise, x) 需遵循以下步骤:

  • 2.3.1 如果 promise 和 x 指向同一个对象,使用 TypeError 作为据因拒绝执行 promise

  • 2.3.2 如果 x 为 Promise ,则使 promise 接受 x 的状态:

    • 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝;
    • 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise;
    • 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise;
  • 2.3.3 如果 x 为对象或者函数:

    • 2.3.3.1 把 x.then 赋值给 then
    • 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    • 2.3.3.3 如果then是函数,将x作为函数的作用域this调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:
      • 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
      • 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
      • 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      • 2.3.3.3.4 如果调用then方法抛出了异常e:
        • 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        • 2.3.3.3.4.2 否则以 e 为据因拒绝 promise
    • 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
  • 2.3.3 如果 x 不为对象或者函数,以 x 为参数执行 promise

  • 如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise。


三,resolvePromise 方法

1,resolvePromise 介绍

如果 then 中方法的返回值 x 为 promsie 类型;
那么将根据返回值 x 决定 promise2 执行成功/失败处理;

根据 promise A+ 规范,创建 resolvePromise 方法,统一处理 Promise 的解析流程;

2,创建 resolvePromise

根据 then 中方法的返回值 x 决定 promise2 走成功/失败(调用 resolve 或 reject);

所以,resolvePromise方法中入参需要promise2、返回值x 、以及 promise2 的resolvereject

function resolvePromise(promise2, x, resolve, reject) {
  // 根据返回值 x 的情况,调用 promise2 的 resolve 或 reject 函数
}

3,使用 resolvePromise

在上一篇中,当 then 中方法返回值 x 为普通值时,调用成功处理 resolve(x);

resolvePromise方法要统一处理返回值 x 的各种情况,所以先使用resolvePromise替换掉resolve(x)

/**
 * 根据返回值 x 情况,调用 promise2 的 resolve 或 reject
 */
function resolvePromise(promise2, x, resolve, reject) {
  resolve(x);// 返回值 x 为普通值的情况
}

class Promise{
  constructor(executor){...}
  then(onFulfilled, onRejected){
    let promise2 = new Promise((resolve, reject)=>{
      if(this.state === DULFILLED){
        try{
          let x = onFulfilled(this.value)
          // resolve(x)  由 resolvePromise 进行统一处理
          resolvePromise(promise2, x, resolve, reject);
        }catch(e){
          reject(e)
        }
      }
      if(this.state === REJECTED){
        try{
          let x = onRejected(this.reason)
          // resolve(x)  由 resolvePromise 进行统一处理
          resolvePromise(promise2, x, resolve, reject);
        }catch(e){
          reject(e)
        }
      }
      if(this.state === PENDING){
        this.onResolvedCallbacks.push(()=>{ 
          try{
            let x = onFulfilled(this.value)
            // resolve(x)  由 resolvePromise 进行统一处理
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e)
          }
        })
        this.onRejectedCallbacks.push(()=>{ 
          try{
            let x = onRejected(this.reason)
            // resolve(x)  由 resolvePromise 进行统一处理
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e)
          }
        })
      }
    });

    return promise2;
  }
}

module.exports = Promise; 

4,promise2 的初始化问题

1,问题分析

代码修改之后,运行将会报错:

Cannot access 'promise2' before initialization
  • 报错:“不能访问 promise2 在初始化之前”;
  • 原因:在 new promise 时,promise2 还没有完成初始化,所以 resolvePromise 中不能访问到 promise2;

2,参考规范

在 Promise A+ 规范中,可以找到此问题的说明:

image.png

image.png

  • 在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的;
  • onFulfilled 或 onRejected 得是在当前事件循环后异步执行的;可以使用 setTimeoutsetImmediateMutationObsereverprocess.nextTick在 then 方法被调用后将创建一个新的栈;

3,问题解决

以 setTimeout 为例,使用 setTimeout 创建宏任务:

  then(onFulfilled, onRejected){
    let promise2 = new Promise((resolve, reject)=>{
      if(this.state === DULFILLED){
        setTimeout(() => {  // 创建宏任务
          try{
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e)
          }
        }, 0);
      }
      if(this.state === REJECTED){
        setTimeout(() => {  // 创建宏任务
          try{
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e)
          }
        }, 0);
      }
      if(this.state === PENDING){
        this.onResolvedCallbacks.push(()=>{ 
          setTimeout(() => {  // 创建宏任务
            try{
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject);
            }catch(e){
              reject(e)
            }
          }, 0);
        })
        this.onRejectedCallbacks.push(()=>{ 
          setTimeout(() => {  // 创建宏任务
            try{
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            }catch(e){
              reject(e)
            }
          }, 0);
        })
      }
    });

    return promise2;
  }

备注:

  • try…catch…只能捕捉在同步代码中发生的异常,所以需要放在 setTimeout 内部;

原理:

  • 通过 setTimeout 创建了宏任务,会在下一个事件循环中被执行;
  • 保证调用 resolvePromise 时,promise2 实例已经创建完成;

四,实现 resolvePromise

1,promise2 === x 的情况

1,参考规范

image.png

  • 如果返回值 x 是 thenable 对象,就调用 then 方法,使用当前 promise 状态作为结果;
  • 如果 promise 和 x 是相同对象,使用 TypeError 作为原因,拒绝这个 promise;

2,测试原生 Promise

let promise2 = new Promise((resolve, reject) => {
  resolve(1);
}).then(() => {
  return promise2; promise2 即为 then 中方法的返回值 x
})

promise2.then(data => {
  console.log(data);
}, err => {
  console.log(err);
})

// 报错:[TypeError: Chaining cycle detected for promise #<Promise>]

现象

  • 会抛出 TypeError 类型错误,promise 死循环;

原因

  • then 中方法返回 promise2,promise2 不会再次调用 resolve 或 reject;

3,功能实现

根据 Promise A+ 规范,代码实现:

function resolvePromise(promise2, x, resolve, reject){
  // promise2 === x,promise 为拒绝状态 TypeError 作为拒因
  if(promise2 === x){// 相同对象:判断引用地址相同
    return reject(new TypeError('发生错误'))
  }
}

4,测试手写 Promise

TypeError: 发生错误

与原生 Promise 表现一致;


2,x 为对象或函数的情况

1,参考规范

image.png

  • 如果 x 是一个 promise,就采用它的状态:
  • 如果 x 是一个对象或函数(有可能是 Promise);

2,代码实现:区分对象或函数/普通值

函数是 JS 的基本类型,可以通过typeof直接判断:

function resolvePromise(promise2, x, resolve, reject) {
  // ...
  // 返回值 x 是对象或函数的情况
  if((typeof x === 'object' && x !== null) || typeof x === 'function'){
  
  }else{// 返回值 x 是普通值的情况
    resolve(x)  // 普通值直接调用 resolve(x)
  }
}

3,x.then 异常的情况

备注: 当返回值 x 是对象或函数时,不一定是 Promise;比如:返回{}空对象;

1,参考规范

image.png

  • let then = x.then;
  • x.then 时可能发生异常,用此异常作为 promise 拒因;

2,代码实现:处理 x.then 异常

function resolvePromise(promise2, x, resolve, reject) {
  // ...
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // 若取 x.then 时发生异常,使用 e 作为拒因
    try {
      let then = x.then;
    } catch (e) {
      reject(e);
    }
  } else {
    resolve(x)
  }
}

4,then 是否为函数的情况

1,参考规范

image.png

  • 如果 then 是一个函数,调用它并让 x 作为它的 this;

2,代码实现:处理 then 是否为函数的 2 种情况

function resolvePromise(promise2, x, resolve, reject) {
  // ...
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function'){
	// then 是函数:就认为 x 为 Promise 类型
      }else{
        resolve(x) // 按普通值处理
      }
    } catch (e) {
      reject(e);
    }
  } else {
    resolve(x)
  }
}

5,x 为 promise 的情况

终点:当 x.then 为 function 时,就认为 x 是 Promise 了;

接下来,就调用 x.then,由它的结果决定执行成功或失败处理;

1,参考规范

image.png

  • 调用 then,并用 x 作为它的 this
  • 第一个参数是 resolvePromise,第二个参数是 rejectPromise
  • resolvePromise 的值叫做 y;rejectPromise 的值叫做 r;

2,代码实现:调用 then,决定 promise2 成功/失败

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('发生错误'))
  }
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function'){
        // 返回值 x 为 Promise,调用 then 看结果
        then.call(x, y => {
          resolve(y)    // 成功
        }, r => {
          reject(r)     // 失败
        });
      }else{
        resolve(x)
      }
    } catch (e) {
      reject(e);
    }
  } else {
    resolve(x)
  }
}

判断是否为 Promise,如果是就它的执行 then 方法,决定 promise2 成功/失败;


五,结尾

本篇,主要实现了 Promise 对返回值 x 的处理,主要涉及到以下几个点:

  • 回顾了 Promise A+ 规范中的相关内容;
  • 根据 Promise A+ 规范实现 resolvePromise 方法;
  • 实现了对 then 中方法的返回值 x 为 promsie 类型的处理;

下一篇,完善 Promise 通过 promise-aplus-tests 测试;


维护记录

  • 20211120
    • 修改部分描述性语句;
    • 修改结尾部分,更新文章摘要;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/183807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亚马逊云科技:大模型的训练和应用门槛亟需降低

在过去的两周里&#xff0c;ChatGPT的热度居高不下&#xff0c;引发全网讨论。虽然AlphaGo这类AI产品也曾引起热议&#xff0c;但是在应用层面终究还是离用户太远了。而ChatGPT更像是「民用级」的产品&#xff0c;真正意义上让AI技术跨入广泛破圈应用时代。在当下&#xff0c;机…

如何准确测试75 Ohm系统的信号?

射频同轴线缆特征阻抗的选择&#xff0c;主要取决于功率容量、衰减强度、可加工性等因素&#xff0c;然而最大功率容量和最小衰减性能对应的特征阻抗是不同的。在射频领域通常采用50 Ohm特征阻抗的原因&#xff0c;就是综合考虑了以上因素。也就是说&#xff0c;50 Ohm特征阻抗…

Vue安装并使用路由和路由器实现页面跳转

前言 想要使用路由和路由器实&#xff0c;必须要知道什么是路由和路由器&#xff0c;可以参考这篇文章&#xff1a;Vue路由和路由器简介 下面通过编写代码演示一下如何使用路由和路由器实现页面跳转。 引入bootstrap.css 本案例所有相关的样式&#xff0c;都是引入的bootst…

基于 Toad 的评分卡模型全流程详解(内含 Python 源码)

不知不觉中&#xff0c;Python 已经在短短几年内一跃成为最热门的编程语言之一&#xff0c;尤其是在数据科学、人工智能和机器学习领域。这除了因为Python相对简单易学&#xff0c;可读性高之外&#xff0c;也有很大一部分原因是因为Python有着良好的开源生态从而产生了许多强大…

Golang如何优雅接入多个远程配置中心?

本文基于viper实现了apollo多实例快速接入&#xff0c;授人以渔&#xff0c;带着大家读源码&#xff0c;详解实现思路&#xff0c;封装成自己的工具类并且开源。 前言 viper是适用于go应用程序的配置解决方案&#xff0c;这款配置管理神器&#xff0c;支持多种类型、开箱即用、…

livedata+ lambda遇到的坑

首先抛出个异常FATAL EXCEPTION: mainProcess: com.lion.media, PID: 5513java.lang.IllegalArgumentException: Cannot add the same observer with different lifecyclesat androidx.lifecycle.LiveData.observe(LiveData.java:199)这是在livedata.observe(this) {// ...}触发…

Android studio Logcat 新版使用命令指南 常用命令集合

只过滤自己的包名 package:mine 过滤自己的包名且只看error级别日志 package:mine level:error 指定消息内容包含12288 message:12288 指定内容不包含12288 -message:12288 其中有 message: 、message~、-message和 -message~ -&#xff1a;对当前标签的过滤结果取反…

智能驾驶 车牌检测和识别(三)《CRNN和LPRNet实现车牌识别(含车牌识别数据集和训练代码)》

智能驾驶 车牌检测和识别&#xff08;三&#xff09;《CRNN和LPRNet实现车牌识别&#xff08;含车牌识别数据集和训练代码&#xff09;》 目录 智能驾驶 车牌检测和识别&#xff08;三&#xff09;《CRNN和LPRNet实现车牌识别&#xff08;含车牌识别数据集和训练代码&#xf…

2022个人年度总结:拒绝无效努力,实现破圈成长。

在从毕业一直到现在&#xff0c;我都会写一篇关于自己的从技术、商业、人情世故以及未来展望的博文&#xff0c;以至于归纳每个时期的自己&#xff0c; 走在互联网开发的边缘&#xff0c;不得不抽出时间鞭策自己学习新知识&#xff0c;未知的知识是 充满好奇的&#xff0c; 就好…

第五章 ArcGIS数据编辑

文章目录第一节 创建新要素方法1 开始编辑、保存编辑、停止编辑2 捕捉的使用3 创建点、线、面4 编辑器中工具的使用5 根据其他要素创建要素6 注记要素编辑和修改第二节 属性编辑常见方法1 属性编辑2 字段计算器3 字段计算器的特殊应用4 计算几何第三节 模版编辑方法1 定义模版2…

Spring学习笔记(一)【BeanUtils.copyProperties方法】

Spring下的BeanUtils.copyProperties方法是深拷贝还是浅拷贝&#xff1f; 一、浅拷贝深拷贝的理解 简单地说&#xff0c;拷贝就是将一个类中的属性拷贝到另一个中&#xff0c;对于BeanUtils.copyProperties来说&#xff0c;必须保证属性名和类型是相同的&#xff0c;因为它是根…

Windows下安装Python和配置easygui

一、需求说明需要学习Python内容&#xff0c;或者是运行Python程序&#xff0c;需要在Windows系统下进行安装配置Python的相关环境&#xff0c;以便让程序能够正常运行使用。二、安装Python2.1、下载Python安装包打开Python官网下载自己Windows系统需要的Python版本选择【Downl…

大宇无限将全部业务系统都部署在亚马逊云科技上

随着移动互联网的高速发展&#xff0c;人们对精神文化内容消费的追求不断提高&#xff0c;利用互联网技术&#xff0c;便捷地享受资讯内容成为了当代人的诉求。为了实现这个目标&#xff0c;大宇无限面临的首要挑战是如何利用大数据系统高效地分析数据、了解用户的消费行为和国…

1、数据库安装超详细教程(MySql5.0版本)

1.1、SQL概述 SQL&#xff0c;一般发音为sequel&#xff0c;SQL的全称Structured Query Language)&#xff0c;SQL用来和数据库打交道&#xff0c;完成和数据库的通信&#xff0c;SQL是一套标准。但是每一个数据库都有自己的特性别的数据库没有,当使用这个数据库特性相关的功能…

Quarkus入门体验,22ms启动一个Web服务

简介 Quarkus是类似于Spring Boot的框架&#xff0c;可以方便大家进行Java开发。利用GraalVM的魔力&#xff0c;能更好的适应云原生的场景&#xff0c;极快的启动速度。 创建项目 在IDEA就直接有创建Quarkus项目的初始化工具&#xff0c;直接根据自己需要填好即可&#xff0…

分享一个可以看历史影像的网站

概述 众所周知&#xff0c;由于某些原因&#xff0c;大家以前经常用的历史地图已经看不了了&#xff0c;那么是否有替代的资源呢&#xff0c;答案是肯定的&#xff0c;而且这个网站的所有者大家都非常的熟悉——Esri&#xff0c;该网站名为World Imagery Wayback&#xff0c;这…

【模型↔关系思考法】如何在一个全新的、陌生的领域快速成为专家?模仿 + 一万小时定律 + 创新...

核心基础:形成模型 行业特性,行业名词术语(模型),行业前规则,行业风险 首先搞懂行业内独有的一些专业名词术语(建立“模型”)的含义,这些是基础。 比如餐饮行业的翻台率、开台率,电商的GMV、SKU、SPU; 通过互联网文章,网站,了解行业的趋势和动向、竞品分析等。 了…

NSS_Round#7部分wp

Web ec_RCE 源码: <!-- A EZ RCE IN REALWORLD _ FROM CHINA.TW --> <!-- By 探姬 --> <?PHPif(!isset($_POST["action"]) && !isset($_POST["data"]))show_source(__FILE__);putenv(LANGzh_TW.utf8); $action $_POST["a…

Jenkins入门(二)Jenkins安装及自动构建部署

一、安装环境 1. 机器要求&#xff1a; 256 MB 内存&#xff0c;建议大于 512 MB 10 GB 的硬盘空间&#xff08;用于 Jenkins 和 Docker 镜像&#xff09; 需要安装以下软件&#xff1a; Java 8 ( JRE 或者 JDK 都可以) 2. 安装JDK 检索可用包 yum search java|grep jd…

Java之JDBC-ResultSet(结果集)

之前的dml语句都返回值都是int表示有改动的行数 那么Select可是要展示数据的 SELECT查询的结果 通过这个ResultSet遍历 然后通过next方法来一行行读取数据 类似于迭代器(肯定不是迭代器) 里面还包含获得的数据元素 相当于这个既包含元素&#xff0c;还能迭代自己的元素 具体你…