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){//通过一个耗时操作得到XsetTimeout(function(){xCallback(10);},Math.floor(500 * Math.random()));
}function fetchY(yCallback){//通过一个耗时操作得到ysetTimeout(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笔记】有关的内容

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue修改node_modules打补丁步骤和注意事项_node_modules 打补丁

1、vue-pdf问题解决及patch-package简介:https://www.jianshu.com/p/d1887e02f8d6 2、使用“黑魔法”优雅的修改第三方依赖包:https://zhuanlan.zhihu.com/p/412753695 3、使用patch-package定制node_modules中的依赖包:https://blog.csdn.…

自动驾驶仿真:Carsim转向传动比设置

文章目录 一、转向传动比概念二、设置转向传动比1、C factor概念2、Steer Kinematics概念3、传动比计算公式 三、转向传动比验证 一、转向传动比概念 转向传动比(Steering Ratio)表示方向盘转动角度与车轮转动角度之间的关系。公式如下: 转向…

电脑怎么录音?分享2种音频录制方法

在日常生活和工作中,我们经常需要录制电脑上的音频,无论是为了记录会议内容、保存网络课程,还是为了制作自己的音频素材,录音功能都显得尤为重要。那么电脑怎么录音?本文将详细介绍2种方法教你如何在电脑上进行录音&am…

Ajax的应用

1. Ajax Ajax是Asynchronous Javascript And XML(异步JavaScript和XML)的缩写。 Ajax技术描述了使用脚本操纵HTTP和Web服务器进行数据交换,在页面不刷新的情况下,实现页面的局部更新。 重点: Ajax 是一种在无需重新加…

C++设计模式——Facade外观模式

一,外观模式简介 外观模式是一种结构型设计模式, 又称为门面模式,也是一种基于创建对象来实现的模式,为子系统中的各组接口的使用提供了统一的访问入口。 外观模式对外提供了一个对象,让外部客户端(Client)对子系统的…

四十七、openlayers官网示例Image Filters——给地图添加锐化、浮雕、边缘等滤镜效果

官网demo示例: Image Filters 这篇讲的是如何给地图添加滤镜。 一看代码,,好家伙,信息量满满,全都看不懂。。。 咱只能一段一段扒。。。 首先添加一个底图到地图上,这个好理解。 const imagery new Til…

macOS vscode常用快捷键

1、shiftoption上下箭头 复制当前行 2、commandd 选定多个相同的单词 先双击选定一个单词,然后按下commandd 依次选中要修改的单词,直接修改即可 3、全局替换某个单词 comandh 4、快速定位到某一行 controlg 5、选中某个区域 shiftoption,然…

Adobe Photoshop cc快速抠图与精致抠图方法

一、背景 Photoshop cc绝对是最好用的抠图and修图软件,但是即使最简单的抠图,每次用时都忘记怎么做,然后再去B站搜,非常费时,下面记录一下抠图过程,方便查阅。 一、Adobe Photoshop快速抠图 选择——主体…

web系统数据库敏感数据处理

一、前言 web系统数据库中保存的公民信息不允许明文存储,比如手机号,身份证号,收货地址等。 二、处理方式 数据库中密文存储,web通过注解的方式对数据加解密处理,下面是处理方法 1、编写接口 public interface E…

提取人脸——OpenCV

提取人脸 导入所需的库创建窗口显示原始图片显示检测到的人脸创建全局变量定义字体对象定义一个函数select_image定义了extract_faces函数设置按钮运行GUI主循环运行显示 导入所需的库 tkinter:用于创建图形用户界面。 filedialog:用于打开文件对话框。 …

【JS】上传文件显示文件的为空,显示的文件参数内容只有uid

上传的文件参数file里面只包含uid,没有其他信息 例子解决办法 例子 例如使用elment ui的el-upload组件上传文件,会导致上传的文件参数file里面只包含uid,没有其他信息,如图: 正确应为如下图: 解决办法 …

MySQL 基本语法讲解及示例(上)

第一节:MySQL的基本操作 1. 创建数据库 在 MySQL 中,创建数据库的步骤如下: 命令行操作 打开 MySQL 命令行客户端或连接到 MySQL 服务器。 输入以下命令创建一个数据库: CREATE DATABASE database_name;例如,创建一…

了解Nest.js

一直做前端开发,都会有成为全栈工程师的想法,而 Nest 就是一个很好的途径,它是 Node 最流行的企业级开发框架,提供了 IOC、AOP、微服务等架构特性。接下来就让我们一起来学习Nest.js Nest.js官网地址 一,了解Nest Cli …

第十二章:会话控制

会话控制 文章目录 会话控制一、介绍二、cookie2.1 cookie 是什么2.2 cookie 的特点2.3 cookie 的运行流程2.4 浏览器操作 cookie2.5 cookie 的代码操作(1)设置 cookie(2)读取 cookie(3)删除 cookie 三、se…

56.SAP MII开发的一个系统响应错误 Error code: ICMETIMEOUT

问题 一个SAP MII开发的项目&#xff0c;最近新增了一个功能&#xff0c;查询数据源量比较大&#xff0c;逻辑有点复杂&#xff0c;大约7-8分钟。发布到生产系统后&#xff0c;发生响应错误&#xff0c;返回 Error code: ICMETIMEOUT <!-- Error code: ICMETIMEOUT -->\r…

Petalinux由于网络原因产生的编译错误(2)--Fetcher failure:Unable to find file

1 Fetcher failure:Unable to find file 错误 如果编译工程遇到如下图所示的“Fetcher failure for URL”或相似错误 出现这种错误的原因是 Petalinux 在配置和编译的时候&#xff0c;需要联网下载一些文件&#xff0c;由于网 络原因这些文件不能正常下载&#xff0c;导致编译…

Vue 路由:一级路由,嵌套路由

1、安装路由插件,因为用的是vue2 所以路由版本要和vue2对应上&#xff0c;所有有3 yarn add vue-router3 2、在main.js里引入 import VueRouter from vue-router Vue.use(VueRouter) 3、新建文件夹 router,创建index.js 4、引入路由插件&#xff0c;并且暴露出来这个路由 5、在…

「TCP 重要机制」滑动窗口 粘包问题 异常情况处理

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;计网 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 滑动窗口&粘包问题&异常情况处理 &#x1f349;滑动窗口&#x1f34c;流量控制&#x1f34c;拥塞控制&#x1f34c;延时应答&…

Docker overlay磁盘使用100%处理方法overlay 100%

一、问题描述 服务器上运行了几个docker容器,运行个一周就会出现overlay 100%的情况&#xff0c;经查找&#xff0c;是容器里生成了很多core.xxx的文件导致的。 二、解决方法 首先通过以下命令查看&#xff1a; df -h 可以看的overlay已经100%了&#xff0c;进入到/var/lib/d…

C++之std::type_identity

目录 1.简介 2.C20的std::type_identity 3.使用 type_identity 3.1.阻止参数推导 3.1.1.模板参数推导过程中的隐式类型转换 3.1.2.强制显式实例化 3.2.阻止推断指引 3.3.类型保持 3.4.满足一些稀奇古怪的语法 4.示例 5.总结 1.简介 std::type_identity 是 C17 引入的…