Promise 出现的原因

在 Promise 出现以前,我们处理一个异步网络请求,大概是这样:

  1. // 请求 代表 一个异步网络调用。
  2. // 请求结果 代表网络请求的响应。
  3. 请求1(function(请求结果1){
  4. 处理请求结果1
  5. })

看起来还不错。
但是,需求变化了,我们需要根据第一个网络请求的结果,再去执行第二个网络请求,代码大概如下:

  1. 请求1(function(请求结果1){
  2. 请求2(function(请求结果2){
  3. 处理请求结果2
  4. })
  5. })

看起来也不复杂。
但是。。需求是永无止境的,于是乎出现了如下的代码:

  1. 请求1(function(请求结果1){
  2. 请求2(function(请求结果2){
  3. 请求3(function(请求结果3){
  4. 请求4(function(请求结果4){
  5. 请求5(function(请求结果5){
  6. 请求6(function(请求结果3){
  7. ...
  8. })
  9. })
  10. })
  11. })
  12. })
  13. })

这回傻眼了。。。 臭名昭著的 回调地狱 现身了。

更糟糕的是,我们基本上还要对每次请求的结果进行一些处理,代码会更加臃肿,在一个团队中,代码 review 以及后续的维护将会是一个很痛苦的过程。

回调地狱带来的负面作用有以下几点:

  • 代码臃肿。
  • 可读性差。
  • 耦合度过高,可维护性差。
  • 代码复用性差。
  • 容易滋生 bug。
  • 只能在回调里处理异常。

出现了问题,自然就会有人去想办法。这时,就有人思考了,能不能用一种更加友好的代码组织方式,解决异步嵌套的问题。

  1. let 请求结果1 = 请求1();
  2. let 请求结果2 = 请求2(请求结果1);
  3. let 请求结果3 = 请求3(请求结果2);
  4. let 请求结果4 = 请求2(请求结果3);
  5. let 请求结果5 = 请求3(请求结果4);

类似上面这种同步的写法。 于是 Promise 规范诞生了,并且在业界有了很多实现来解决回调地狱的痛点。比如业界著名的 Q 和 bluebird,bluebird 甚至号称运行最快的类库。

什么是 Promise

Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。

代码书写比较

还是使用上面的网络请求例子,我们看下 Promise 的常规写法:

  1. new Promise(请求1)
  2. .then(请求2(请求结果1))
  3. .then(请求3(请求结果2))
  4. .then(请求4(请求结果3))
  5. .then(请求5(请求结果4))
  6. .catch(处理异常(异常信息))

比较一下这种写法和上面的回调式的写法。我们不难发现,Promise 的写法更为直观,并且能够在外层捕获异步函数的异常信息。

API

Promise 的常用 API 如下:

  • Promise.resolve(value)
  1. 类方法,该方法返回一个以 value 值解析后的 Promise 对象
  2. 1、如果这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled
  3. 2、如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
  4. 3、其他情况以该值为成功状态返回一个 Promise 对象。

上面是 resolve 方法的解释,传入不同类型的 value 值,返回结果也有区别。这个 API 比较重要,建议大家通过练习一些小例子,并且配合上面的解释来熟悉它。如下几个小例子:

  1. //如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
  2. function fn(resolve){
  3. setTimeout(function(){
  4. resolve(123);
  5. },3000);
  6. }
  7. let p0 = new Promise(fn);
  8. let p1 = Promise.resolve(p0);
  9. // 返回为true,返回的 Promise 即是 入参的 Promise 对象。
  10. console.log(p0 === p1);

传入 thenable 对象,返回 Promise 对象跟随 thenable 对象的最终状态。

  1. ES6 Promises 里提到了 Thenable 这个概念,简单来说它就是一个非常类似 Promise 的东西。最简单的例子就是 jQuery.ajax,它的返回值就是 thenable 对象。
    但是要谨记,并不是只要实现了 then 方法就一定能作为 Promise 对象来使用。
  1. //如果传入的 value 本身就是 thenable 对象,返回的 promise 对象会跟随 thenable 对象的状态。
  2. let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象
  3. promise.then(function(value){
  4. console.log(value);
  5. });

返回一个状态已变成 resolved 的 Promise 对象。

  1. let p1 = Promise.resolve(123);
  2. //打印p1 可以看到p1是一个状态置为resolved的Promise对象
  3. console.log(p1)
  • Promise.reject

类方法,且与 resolve 唯一的不同是,返回的 promise 对象的状态为 rejected。

  • Promise.prototype.then

实例方法,为 Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。

  • Promise.prototype.catch

实例方法,捕获异常,函数形式:fn(err){}, err 是 catch 注册 之前的回调抛出的异常信息。

  • Promise.race

类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。 。

  • Promise.all

类方法,多个 Promise 任务同时执行。

如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。

  • ...

以上几种便是 Promise 的常用 API,掌握了这些,我们便可以熟练使用 Promise了。

一定要多练习,熟练掌握,否则一知半解的理解在面试时捉襟见肘。

如何理解 Promise

为了便于理解 Promise,大家除了要多加练习以外,最好的方式是能够将Promise的机制与现实生活中的例子联系起来,这样才能真正得到消化。

我们可以把 Promise 比作一个保姆,家里的一连串的事情,你只需要吩咐给他,他就能帮你做,你就可以去做其他事情了。

比如,作为一家之主的我,某一天要出门办事,但是我还要买菜做饭送到老婆单位(请理解我在家里的地位。。)

出门办的事情很重要,买菜做饭也重要。但我自己只能做一件事。

这时我就可以把买菜做饭的事情交给保姆,我会告诉她:

  • 你先去超市买菜。
  • 用超市买回来的菜做饭。
  • 将做好的饭菜送到老婆单位。
  • 送到单位后打电话告诉我。

我们知道,上面三步都是需要消耗时间的,我们可以理解为三个异步任务。利用 Promise 的写法来书写这个操作:

  1. function 买菜(resolvereject) {
  2. setTimeout(function(){
  3. resolve(['西红柿''鸡蛋''油菜']);
  4. },3000)
  5. }
  6. function 做饭(resolve, reject){
  7. setTimeout(function(){
  8. //对做好的饭进行下一步处理。
  9. resolve ({
  10. 主食: '米饭',
  11. 菜: ['西红柿炒鸡蛋''清炒油菜']
  12. })
  13. },3000)
  14. }
  15. function 送饭(resolvereject){
  16. //对送饭的结果进行下一步处理
  17. resolve('老婆的么么哒');
  18. }
  19. function 电话通知我(){
  20. //电话通知我后的下一步处理
  21. 给保姆加100块钱奖金;
  22. }

好了,现在我整理好了四个任务,这时我需要告诉保姆,让他按照这个任务列表去做。

这个过程是必不可少的,因为如果不告诉保姆,保姆不知道需要做这些事情。。(我这个保姆比较懒)

  1. // 告诉保姆帮我做几件连贯的事情,先去超市买菜
  2. new Promise(买菜)
  3. //用买好的菜做饭
  4. .then((买好的菜)=>{
  5. return new Promise(做饭);
  6. })
  7. //把做好的饭送到老婆公司
  8. .then((做好的饭)=>{
  9. return new Promise(送饭);
  10. })
  11. //送完饭后打电话通知我
  12. .then((送饭结果)=>{
  13. 电话通知我();
  14. })

至此,我通知了保姆要做这些事情,然后我就可以放心地去办我的事情。

请一定要谨记:如果我们的后续任务是异步任务的话,必须return 一个 新的 promise 对象。

如果后续任务是同步任务,只需 return 一个结果即可。

我们上面举的例子,除了电话通知我是一个同步任务,其余的都是异步任务,异步任务 return 的是 promise对象。

除此之外,一定谨记,一个 Promise 对象有三个状态,并且状态一旦改变,便不能再被更改为其他状态。

  • pending,异步任务正在进行。
  • resolved (也可以叫fulfilled),异步任务执行成功。
  • rejected,异步任务执行失败。

Promise的使用总结

Promise 这么多概念,初学者很难一下子消化掉,那么我们可以采取强制记忆法,强迫自己去记住使用过程。

首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象。

  • 1、new Promise(fn)
  • 2、Promise.resolve(fn)

然后调用上一步返回的 promise 对象的 then 方法,注册回调函数。

  • then 中的回调函数可以有一个参数,也可以不带参数。如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数。比如
  1. new Promise(fn)
  2. .then(fn1(value){
  3. //处理value
  4. })
  • 最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。

通常按照这三个步骤,你就能够应对绝大部分的异步处理场景。用熟之后,再去研究 Promise 各个函数更深层次的原理以及使用方式即可。

看到这里之后,我们便能回答上面的问题 4 和问题 5了。

Promsie 与事件循环

Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。

同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环。

关于 Promise 在事件循环中还有一个 微任务的概念(microtask),感兴趣的话可以进一步了解,虽然和浏览器端有些不同,但是Promise 微任务的执行时机相差不大。

Promise 的升级 async/await

ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致。上面的列子可以写成这样:

  1. (async ()=>{
  2. let 蔬菜 = await 买菜();
  3. let 饭菜 = await 做饭(蔬菜);
  4. let 送饭结果 = await 送饭(饭菜);
  5. let 通知结果 = await 通知我(送饭结果);
  6. })();

是不是更清晰了有没有。需要记住的是,async/await也是基于 Promise 实现的,所以,我们仍然有必要深入理解 Promise 的用法。

答应我,这次必须搞懂!痛点难点Promise。(小点心async/await,基于Promise的更优方案)的更多相关文章

  1. 据说60%的Java程序员不明白分布式一致性?这次彻底搞懂!

    前言 在计算机科学领域,分布式一致性是一个相当重要且被广泛探索与论证问题,首先来看三种业务场景. 1.火车站售票 假如说我们的终端用户是一位经常坐火车的旅行家,通常他是去车站的售票处购买车票,然后拿着 ...

  2. 7种jvm垃圾回收器,这次全部搞懂

    前言 之前我们讲解了jvm的组成结构与垃圾回收算法等知识点,今天我们来讲讲jvm最重要的堆内存是如何使用垃圾回收器进行垃圾回收,并且如何使用命令去配置使用这些垃圾回收器. 堆内存详解 上面这个图大家应 ...

  3. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...

  4. 基础篇|一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

  5. c#代码 天气接口 一分钟搞懂你的博客为什么没人看 看完python这段爬虫代码,java流泪了c#沉默了 图片二进制转换与存入数据库相关 C#7.0--引用返回值和引用局部变量 JS直接调用C#后台方法(ajax调用) Linq To Json SqlServer 递归查询

    天气预报的程序.程序并不难. 看到这个需求第一个想法就是只要找到合适天气预报接口一切都是小意思,说干就干,立马跟学生沟通价格. ​ ​不过谈报价的过程中,差点没让我一口老血喷键盘上,话说我们程序猿的人 ...

  6. 搞懂分布式技术28:微服务(Microservice)那点事

    搞懂分布式技术28:微服务(Microservice)那点事 微服务(Microservice)那点事 肥侠 2016-01-13 09:46:53 浏览58371 评论15 分布式系统与计算 微服务 ...

  7. 从源码的角度彻底搞懂 HandlerMapping 和 HandlerAdapter

    彻底搞懂 HandlerMapping和HandlerAdapter 知识点的回顾: 当Tomcat接收到请求后会回调Servlet的service方法,一开始入门Servlet时,我们会让自己的Se ...

  8. ERP 到底是什么? 一则故事搞懂ERP

    你知道什么是ERP? ERP是什么? 你知道什么是ERP吗? (通俗易懂版) 一个故事搞懂“ERP” 一天中午,丈夫在外给家里打电话:“亲爱的老婆,晚上我想带几个同事回家吃饭可以吗?”(订货意向) 妻 ...

  9. React16源码解读:开篇带你搞懂几个面试考点

    引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...

随机推荐

  1. JAVA笔记11__File类/File类作业/字节输出流、输入流/字符输出流、输入流/文件复制/转换流

    /** * File类:文件的创建.删除.重命名.得到路径.创建时间等,是唯一与文件本身有关的操作类 */ public class Main { public static void main(St ...

  2. Linux 启动/关闭 oracle 数据库

    1.启动 1.1 启动监听 :lsnrctl start 1.2 启动数据库:sqlplus /nolog    conn /as sysdba(或者两句一起:sqlplus sys/ as sysd ...

  3. Java Logback简易教程

    本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可. 一.前言 本文以一个简单的项目为例,一步步展示logback的同步和异步配置方法,并且配置的日志要求满足阿里巴巴Java开发手册- ...

  4. Python基础(定制类)

    文章转载自廖雪峰老师Python课程博客,仅供学习参考使用看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的. __slots__我们已经知道 ...

  5. 菜鸡的Java笔记 数据表与简单java类映射

    利用实际的数据表实现表与类的操作转换        简单java类是整个项目开发中的灵魂所在,它有自己严格的开发标准,而最为重要的是它需要于数据表是完全对应的        不过考虑到现在没有接触到过 ...

  6. Spring Boot核心注解

    (1)@SpringBootApplication 代表SpringBoot的启动类 (2)@SpringBootConfiguration 通过bean对象来获取配置信息 (3)@Configura ...

  7. 关于【【故障公告】数据库服务器 CPU 近 100% 引发的故障(源于 .NET Core 3.0 的一个 bug)】IS NOT NULL测试

    测试如图,Core_Users的PhoneNumber可为空,存在索引,记录数1500000+ 增加is not null,查询计划消耗增加了一个0%的筛选器消耗,IO消耗如下一模一样 如果是IS N ...

  8. [spojQTREE5]Query on a tree V

    合理的正解大概是动态点分治,这里给出其实现 1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 100005 4 st ...

  9. [源码解析] PyTorch 分布式(10)------DistributedDataParallel 之 Reducer静态架构

    [源码解析] PyTorch 分布式(10)------DistributedDataParallel之Reducer静态架构 目录 [源码解析] PyTorch 分布式(10)------Distr ...

  10. k8s-Pod污点与容忍

    目录 Pod污点与容忍 大白话先解释一下污点与容忍 为什么要用污点和容忍? 官方解释 Taints参数 标记污点 容忍污点 取消所有节点污点 Pod污点与容忍 大白话先解释一下污点与容忍 污点:被打上 ...