JavaScript和promise——0_1 promise

文章目录

  • 是什么?
  • 未来值
    • 回调和未来值
      • 在回调环境下这么和未来值交互?
      • 群居的未来值
        • 其他的解决方案
      • 这样写可以实现目标效果。可是,这样写优雅吗?
    • 英雄登场
      • 关键词:then
      • 关键词:回调
  • 为什么promise不需要start函数
  • promise和不靠谱的回调
    • 不靠谱的回调
      • 不被信任的回调,到底会做出些什么诡异操作?
      • 这些问题是怎么出现的?
        • 调用回调过早
        • 调用回调过晚
        • 回调次数不确定(甚至可能是0次)* & *未能传递所需的环境和参数
        • 异常丢失
    • Promise的解决方案
      • *调用回调过早*
      • *调用回调过晚*
      • *回调次数不确定*
      • 未能传递所需的环境和参数
      • 异常丢失
  • 为什么promise值得被信任
      • 那为什么promise就比回调更值得被信任呢?

天不生Promise,则JavaScript万古如长夜

是什么?

Promise是JavaScript的一种替换回调的解决方案,绝大多数情况下,是在程序出现异步任务的时候使用他。他诞生于社区,原先只是一种约定俗成的范式,直到 ES6 提供了原生Promise对象的实现,至此Promise成为了JavaScript的语言标准




未来值

在描述Promise是如何改变JavaScript的异步生态之前,我们需要先理解一个概念——未来值

未来值是一个凭证,他意味着某个在将来的某个时间点程序将会获取到的一个值。
试想一下,如果没有这个值的存在,你依然需要异步回调吗?
答案是否定的


这样你会说,不对啊。那串行的动画,难道就不是没有未来值而需要异步的动作吗?一串连贯的动画之间存在顺序关系,但是动画和主线程之间是并行的

如果你这么想,那说明你的问题很严重,因为:

JavaScript中只有不在此时立刻开始的异步任务,而不存在并行的任务。因为JavaScript引擎是基于事件循环实现的。他永远是单线程的!


所以我们讨论的前提,就是我们面对的并不是多线程,而是一段不知道会在将来哪个时间点突然执行完成的异步任务。正因为这种不确定性,所以我们需要一个机制,来确保无论这个异步任务在什么时候执行完毕,程序都可以按照我们所预想的那样执行

通过这个不确定执行时间的异步任务,有时候我们可以得到一个返回值,这个值,我们就把他称为未来值


我们的异步回调,很明显就需要围绕这个不确定什么时候会出现的未来值去操作。而且很显然,我们在回调中需要获取这个未来值,并操作他



回调和未来值

未来值的概念是从JavaScript诞生一来就一直存在的了。比如Ajax中服务器返还的内容,某个定时任务中被修改的状态。就像我们之前说的那样,在promise出现之前,回调是我们处理异步的唯一方式


在回调环境下这么和未来值交互?

回调采用的是一种类似托孤的方式。由于JavaScript引擎没有时间的概念,也没办法直接和宿主平台中的内容打交道。所以JavaScript在把异步任务提交给宿主之后,JavaScript引擎是不知道这段代码会在什么时候,怎么被执行的。而要在未来值出现后回调的函数里面的内容也会在提交异步任务的时候一起被提交给宿主,由宿主在调用了异步任务后,自己去调用回调函数

但是我们知道,宿主调用回调的过程是不值得被信任的.


群居的未来值

一个很关键的问题,未来值未必是孤立的。这个未来值可能会和某个已有的变量有关联;最极端的状况下, 也许未来值会和某个其他未来值存在关联


试想这样一个需求,我们有x和y两个字段,这两个字段我们都需要各自通过一个耗时操作才能得到他们。
最终,在回调函数中,我们需要把x和y相加,得到z。

  1. 我不希望程序因为等待这两个耗时操作而让程序假死,所以获取x和y的动作一定是异步的,而x和y分别是这两个异步任务的返回值(也就是未来值)
  2. x和y各自有不同的异步任务,所以也有不同的回调函数。在两个函数中要共享相同的变量,你必须要升域,把x和y定义于两个回调函数之外,就像这样:
//采用回调的方式实现 x和y相加
function fetchX(xCallback){
    //通过一个耗时操作得到X
    setTimeout(function(){
        xCallback(10);
    },Math.floor(500 * Math.random()));
}

function fetchY(yCallback){
    //通过一个耗时操作得到y
    setTimeout(function(){
        yCallback(5);
    },Math.floor(500 * Math.random()));
}

function add(getX,getY){
    let x,y;

    //回调函数定义
    function xCallback(value){
        x = value;

        if(y !== undefined){
            console.log("我是通过xCallback得到的结果=" + (x + y));
        }
    }

    function yCallback(value){
        y = value;
        if(x !== undefined){
            console.log("我是通过yCallback得到的结果=" + (x + y));
        }
    }

    //调用
    getX(xCallback);
    getY(yCallback);
}

在实际执行的过程中,你会发现光升域是不够的。因为最后的相加动作,必须是在x和y的任务都执行完的情况下才可以执行。那在每一个回调函数里面,我们都需要判断另一个变量是否已经有值了(相当于判断另一个异步任务是否已经执行完了),然后再决定是否继续执行下一步 以此来实现异步任务之间的通讯

其他的解决方案

除了通过在回调函数里面通过异步任务之间的协作的方法意外以外,你还有第二种解决方案。即 链状的回调
简单的来说就是在获取x的回调中触发y的异步任务。这样你就可以保证在y的异步任务完成的时候,x和y一定都是有值的


这样写可以实现目标效果。可是,这样写优雅吗?

现在还只是x和y,如果是N个对象之间的联动,那岂不是妥妥的回调地狱?



英雄登场

很显然,上面那个问题的答案是否定的。于是,promise登场了
上文的处理方式,虽然可以完成我们的需求。但是他总让我们感觉很复杂,因为我们要在每个回调里面都要去判断可能出现的所有情况。


那有没有可能我们把情况统一呢?
既然我这么问了,那答案就一定是肯定的。对于异步任务来说,可能出现的情况无非就两种,完成 or 未完成


在promise中,我们把 所有的情况都视为未完成,视为将来要发生的事情。用promise来表达刚刚那个例子,那他长这样:

function addByPromise(xPromise,yPromise){
    return Promise.all([xPromise,yPromise]);
}

addByPromise(new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve(10);
    },1000);
}),new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve(5);
    },1000);
}))
.then(function(values){
    //当程序走到这里的时候,事实上JavaScript引擎已经取回了程序的执行权
    console.log("使用Promise实现加法同步,结果为:" + (values[0] + values[1]));
});

关键词:then

显然,在then的回调函数里面的那个value参数,就是我们上面聊的【未来值】。而这个【未来值】其实也就是promise的本质。
promise翻译过来的意思是承诺。而value对于then中的回调函数来说,相当于一个对【未来值】的承诺。promise可以保证你写入的then回调,一定会被执行,并获取到正确的【未来值】。

Promise是怎么做到的呢?


关键词:回调

注意到了吗,我刚刚又提到了这个关键字,回调。

没错,在promise中也有回调。那你就会说了,既然promise中有回调,那就必然会出现因为回调而存在的问题(回调地狱且不提,单说宿主可信任程度的问题)

是的,是存在这些问题,但是promise帮我们解决了。怎么解决的呢?这就涉及到了promise的两个重要特性:状态不可逆&值不可变
promise有三种状态,而相同的api,会根据promise当前状态的不同回应不同的行为:

  1. pending:进行中

    promise一被创建出来,就是pending状态。fulfilled和rejected是同级的,一次异步任务只能是fulfilled/rejected

  2. fulfilled:已成功

    promise的状态是 不可逆 的,只要被切换成fulfilled/rejected之后,就不可能再回到pending中

  3. rejected:已异常

托孤式 的回调不同,promise的回调只做一件事:

  • 如果,异步任务成功执行,并拿到【未来值】,把【未来值】传给JavaScript引擎,引擎会把promise的状态从pending转换成fulfilled
  • 如果,异步任务执行失败,并抛出异常,把异常传给JavaScript引擎,引擎会把promise的状态从pending转换成rejected

无论是哪个分支,当JavaScript引擎拿到 【未来值】or 异常 的时候,他就会重新获得对这个任务的主导权。

接下来JavaScript引擎会把接收到的【未来值】 or 异常 传给promise的回调中。注意,这里的回调就不用发送给宿主了,因为这里不存在异步任务了。而是由JavaScript引擎管理的回调




为什么promise不需要start函数

细心点你可能会发现,在我们创建promise的时候,其实异步任务就已经开始执行了。


那为什么我们不需要担心我们还没有写入then回调的时候,异步任务就执行完的情况呢?
在Java、C#这些语言的多线程中,我们可是习惯了先把所有的准备动作都做好再开始线程的做法啊?

  • 第一个原因
    你注意到了吗,promise并不是自动执行then回调的,他用了一个resolve函数,然你在异步任务里面显式的调用then回调

  • 第二个原因
    promise的内部构造显然是参考了【状态(State)】的设计模式。相同的api会根据调用时promise不同的状态,执行不同的操作。
    也就是说,当我调用then的时候,promise只可能处于 未完成&已完成 的状态其中一种。

    • 如果,我调用then的时候promise未完成,那么promise会把我写入的回调维护起来,等完成后一起调用
    • 如果,我调用then的时候promise已经完成了,那么promise也不会立刻调用我写入的回调。promise会异步执行我写入的回调




promise和不靠谱的回调

不靠谱的回调

如果说回调地狱带来的困扰勉强还可以克服的话,那么不稳定的宿主环境带来的问题,就是无论如何都不能忽视的了。因为我们对回调的行为的无法确定,所以我们失去了对回调的信任。


不被信任的回调,到底会做出些什么诡异操作?

  • 调用回调过早
  • 调用回调过晚
  • 回调次数不确定
  • 未能传递所需的环境和参数
  • 异常丢失

这些问题是怎么出现的?

调用回调过早

异步任务总是有长有短,他可不会等你把回调托付给宿主后再让自己执行完。这样一来异步任务和回调之间就出现了一种竟态,总会有你还没有写入回调,异步任务就执行完的情况。
这样的话宿主就抓不到回调,从而也就不执行回调了


调用回调过晚

当我们对一个异步任务有多个回调的时候,通常我们是没有办法判断这些回调的执行顺序的。我们知道,对于回调来说,每次触发回调,回调函数都需要到事件循环的结尾重新排队的

回调次数不确定(甚至可能是0次)* & *未能传递所需的环境和参数

这两个问题很常见,会不会出现完全取决于宿主的心情。根据定义,正确的回调调用次数应该是1次。但是有些宿主就是这么叛逆

异常丢失

这里的异常是指异步任务在执行的时候出现的异常
异步任务是在宿主环境被调用的,所以抛出的异常也会先体现到宿主环境中。是否要捕获并处理这些异常,需要你自己在回调中定义



Promise的解决方案

调用回调过早

调用过早根源在于回调函数的输入时间点和异步任务执行完成的时间点存在竟态
而在promise中,只存在“未来”才要完成的异步任务,其中的原理上文已经描述得很清楚了


调用回调过晚

在我们显性调用resolve或者reject触发then之后。剩下的回调已经由JavaScript引擎掌握了主动权了,更何况在ES6之后,还有任务队列的概念。promise是在任务队列中执行的 微任务。根本不需要担心调用过晚的问题
而且,then之间是用链状模式连接的,就像这样:

在这里插入图片描述

promise可以保证这一段一定输出ABC

虽然输出A的时候调用了C的then,但是并不影响已经注册的B

但是不同的promise之间的then执行顺序是不确定的,就像这样:

在这里插入图片描述

这段代码的结果是:A B

因为当promise1解析promise3的时候,这个解析的动作是放在promise1的then回调里面的。而我们说过,promise的then回调是要进任务队列的。而在任务队列中,新加入的对promise3的解析动作,就没有promise2的优先级高了。


回调次数不确定

首先要达成一个共识,没有任何东西(甚至JavaScript错误)可以阻止promise向你履行他的承诺。如果你的promise的定义是完整的(即,带有resolve、reject和catch的任务),那么他一定会调用其中的某一个。

  • 一次都不调用
    除开上面说的所有不确定性以外,还是有一种情况会让promise的回调不执行。那就是你输入的异步操作无法结束。那么promise就永远拿不到【未来值】。这时候一般我们会给promise中的异步任务增加一个“定时器”,当promise超时的时候,就视为异步操作无法结束。就像这样:

    在这里插入图片描述

    输出:

    在这里插入图片描述

  • 调用次数过多
    promise只可能通过一次决议,所以不用担心调用次数过多的问题。promise的then回调,有且只会跑一次


未能传递所需的环境和参数

这个问题和上一个问题一起被解决了
而当你有多个【未来值】的时候,promise会把他们组合形成一个数组,一起传给then回调


异常丢失

promise通过在定义异步任务的时候所传入的reject回调来实现对这个问题的处理。
你可以显性的调用reject(就像上面那个例子一样),但是即使你没有那样做,当出现异常的时候,promise也会自动调用reject回调。因为promise拿不到【未来值】
所以除非你没有定义reject,否则promise中就不会出现异常丢失的情况




为什么promise值得被信任

我们使用promise的初衷,是因为我们发现回调不值得被信任。所以我们追求一种新的方式以实现异步回调的效果
但是现在我们发现,就算我们用promise,也没有摆脱回调。promise只是改变了回调的位置和方式

那为什么promise就比回调更值得被信任呢?

要回答这个问题,要先回答我们到底在不信任什么
我们把异步任务托付给宿主之后,就失联了
但是我们用promise创建异步回调的时候,他会监视整个异步任务的执行过程,虽然没办法直接参与。无论异步任务最终有没有被完成,promise都一定会给你一个结果,而不是像回调一样,凭空消失





万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【JavaScript笔记】有关的内容

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】CART决策树算法的核心思想及其大数据时代银行贷款参考案例——机器认知外界的重要算法

目录 引言 概述 CART决策树的特点 核心思想 减少不确定性的指标 基尼系数(Gini Index) 分类错误率 熵 银行实例 背景 数据准备 模型构建 模型评估与优化 应用与结果 代码示例 ✈✈✈✈引言✈✈✈✈ CART算法既可以用于分类问题&#xff0…

Simulink代码生成: 状态机的其他建模方法

本文研究状态机建模的一些方法和技巧。 文章目录 1 引入2 状态机建模方法2.1 状态机中的计时2.2 状态机中的计数2.3 转移顺序 3 总结 1 引入 博主一直很喜欢用Simulink中的状态机建模,在这里想记录一下自己平时使用Stateflow建模的心得。因为自身行业所限&#xff…

深入理解并打败C语言难关之一————指针(3)

前言: 昨天把指针最为基础的内容讲完了,并且详细说明了传值调用和传址调用的区别(这次我也是做到了每日一更,感觉有好多想写的但是没有写完),下面不多废话,下面进入本文想要说的内容 目录&#…

【数据结构】第十七弹---C语言实现选择排序

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、选择排序 1.1、基本思想 1.2、代码实现 1.3、代码测试 1.4、时空复杂度分析 总结 1、选择排序 1.1、基本思想 选择排序是一种简单直观的比…

【测试专题】系统测试报告(原件Word)

软件测试报告在软件开发过程中起着至关重要的作用,主要有以下几个主要原因: 1、确保软件质量 2、提供决策支持 3、记录测试过程和结果 4、促进沟通和协作 5、符合标准和法规要求 6、改进测试流程和策略 7、降低风险 软件开发全套资料获取进主页或者本文末…

如何判断三相交流电子负载的性能

三相交流电子负载是模拟实际负载的设备,用于测试电源、变频器、逆变器等电力电子设备的性能。在购买和使用三相交流电子负载时。 三相交流电子负载能够稳定输出的最大有功功率,额定功率越高,说明负载的承载能力越强。在选择三相交流电子负载时…

计算机相关专业是否仍是“万金油”的选择?

亲爱的朋友们: 2024 年高考已然落幕,数百万高三学子站在了人生的重要十字路口,面临着选择大学专业这一关键抉择。在这个节点上,计算机相关专业是否还能被称为“万金油”的选择呢? 相信大家都知道,在最近这几…

【前端项目笔记】2 主页布局

主页布局 element-ui提供的组件名称就是它的类名 ☆☆ CSS选择器: (1)基本选择器 类型选择器 p/span/div…… 类选择器 (.classname) ID选择器 (#idname) 通配选择器 ( * ) (2)属性选择器 选择具有特定属性或属性值的…

k8s删除状态为 Terminating 的pod

卸载calico pod时候pod资源状态会卡在terminating,这时候需要手动进行删除 使用以下命令即可 kubectl delete pod podName -n NAMESPACE --force --grace-period0记住一定要加命名空间,不然会报错没有找到

Android可穿戴设备世界之旅

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 介绍 Android通过在电视、穿戴和汽车等各种电子模块中扩展下一代应用开发概念,扩展了其整个范围和可…

计算机网络:6应用层

概述 客户/服务器方式和对等方式 客户/服务器(Client/Server,C/S)方式 客户和服务器是指通信中所涉及的两个应用进程。 客户/服务器方式所描述的是进程之间服务和被服务的关系。 服务器总是处于运行状态,并等待客户的服务请求。 …

C# + easyui 写的一个web项目

用C# easyui 来开发,其实就是为了开发速度,用easyui可以一天写很多页面,比一些低代码平台还快。 登陆页面 主界面 记录数统计 家庭信息采集表 新建家庭 家庭成员 低保、五保人员帮扶情况登记表 低保、五保人员帮扶情况登记表的新增和编辑 治…

【星海随笔】云解决方案学习日志篇(二) kafka、Zookeeper、Fielbeat

Elastic 中国社区官方博客 https://blog.csdn.net/ubuntutouch/category_9209092.html Kafka kafka的源代码是基于Scala语言编写的,运行在Java虚拟机(即:JVM)上。因此,在安装kafka之前需要先安装JDK Kafka 为什么依赖 Zookeepe…

数据库、中台、报表平台之间的关系

我最近在接触报表平台和中台,发现他们跟我平常用的数据库不是一个东西。然后,我开始了摸索他们的过程,终于,我在理清他们的关系以后,简单写一个入门级的区分。 数据库: 定义: 数据库是被长期存…

HarmonyOS开发日记 :自定义节点,实现 UI 组件 动态创建、更新

引言 UI动态操作包含组件的动态创建、卸载、更新等相关操作。 通过组件预创建,可以满足开发者在非build生命周期中进行组件创建,创建后的组件可以进行属性设置、布局计算等操作。之后在页面加载时进行使用,可以极大提升页面响应速度。 UI …

S32K3通过S32DS实现:S32K3如何将FLASH驱动放到RAM里面、RAM如何实现软件复位数据不丢失操作。

目录 1、概述 2、默认flash存放位置展示 3、通过默认的链接文件将flash放置到RAM 4、通过修改启动与链接文件将flash放在RAM 5、RAM热复位数据不丢失 1、概述 在通过RTD的SDK也好MCAL也好,始终存在一个问题,生成的代码除了看门狗模块,默认都是放在flash里面,按照正常逻…

【数据结构与算法】运算受限的线性表(栈,队列)重要知识点详解

栈和队列是什么样的线性表? 栈(Stack)和队列(Queue)都是运算受限的线性表。 栈:栈是一种特殊的线性表,只允许在一端(通常称为“顶端”)进行插入和删除操作。栈遵循后进先出&#x…

AI播客下载:The TWIML AI Podcast (机器学习与人工智能周刊)

机器学习和人工智能正极大地改变着企业的运营方式和人们的生活方式。TWIML AI 播客将机器学习和人工智能领域的顶尖思想和理念带给了一个广泛的、有影响力的社区,这个社区包括机器学习/人工智能研究人员、数据科学家、工程师以及技术娴熟的商业和 IT 领导者。主持人…

EasyRecovery下载_EasyRecovery官方下载_2024最新版软件安装包附加详细安装步骤

EasyRecovery中文版是一款操作安全、恢复性比较高的数据恢复工具,小伙伴们可以使用EasyRecovery恢复各种各样被删除的文件、视频、图片等。EasyRecovery还可以支持恢复从硬盘、光盘、U盘、数码相机、手机等各种设备中恢复被删除或丢失的文件,只是使用Eas…

【验证码识别】Yolov8实战某验3空间推理点选验证码,目标检测,语义分割,颜色分类。

【验证码识别】Yolov8实战某验3空间推理点选验证码,目标检测,语义分割,颜色分类。 文章目录 【验证码识别】Yolov8实战某验3空间推理点选验证码,目标检测,语义分割,颜色分类。声明1.空间推理验证码&#xf…