返回顶部
首页 > 资讯 > 前端开发 > JavaScript >彻底搞懂 javascript的Promise
  • 552
分享到

彻底搞懂 javascript的Promise

2024-04-02 19:04:59 552人浏览 八月长安
摘要

目录一、为什么要引入Promise?Promise解决了什么问题?Promise有哪些具体的使用场景?二、手写Prromise身上的方法手写Promise.all手写Promise.

一、为什么要引入Promise?

在介绍本章之前,首先先抛出几个问题:

  • Promise解决了什么问题?
  • Promise有哪些具体的使用场景?

Promise解决了什么问题?

1.回调地狱问题

在没有Promise之前,前端获取数据往往需要通过回调函数层层嵌套的方式来解决异步问题,例如下面这段代码实例:

// 回调地狱实例
// 奶茶函数
function getTea(fn) {
  setTimeout(() => {
    fn('获取到一杯奶茶')
  },2000)
}
// 面包函数
function getBread(fn) {
  setTimeout(() => {
    fn('获取到一个面包')
  },100)
}
// 如果必须按照顺序获取,而不是根据时间,要求是先获取到奶茶后获取到面包。
getTea(function(data) {
  console.log(data);
  getBread(function(data) {
    console.log(data);
  }) 
})

2.可读性问题

通过Promise我们可以将上面的代码重写为下面的方式,明显这样可读性更高。

// 下面解释下,如何通过Promise来解决回调地狱的问题
function getTea() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('获取到一杯奶茶')
    }, 2000)
  })
}
function getBread() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('获取到一个面包')
    }, 500)
  })
}
getTea()
  .then(res => {
    console.log(res);
    return getBread();
  })
  .then(res => {
    console.log(res);
  })

3.信任问题(也叫回调多次执行问题)

传统的回调函数无法保证只被执行一次,回调函数还要可能被执行其他操作,而Promise调用且仅调用一次resolve,不会产生回调多次执行的问题,所以Promise很好的解决了第三方库多次调用回调的问题。

Promise有哪些具体的使用场景?

  • 场景1:将图片的加载写成一个Promise,图片一旦加载完成,Promise的状态就会发生变化。
  • 场景2:当下一个异步请求需要依赖上一个请求结果的时候,可以通过链式操作解决问题。
  • 场景3:通过all()实现多个请求合并在一起,汇总所有的请求结果,只需设置一个loading即可。
  • 场景4:通过race()可以设置图片请求超时。

二、手写Prromise身上的方法

手写Promise.all

Promise.all的特点是接收的是一个可迭代对象,当这个可迭代对象中的所有元素都执行成功会返回一个数组,一个出错则立即返回错误。

function myPromiseAll(iterable) {
  // 首先明确要返回的对象是一个Promise
  return new Promise((resolve,reject) => {
    // 首先将可迭代对象转换为数组
    const promises = Array.from(iterable);
    let flag = 0;
    const result = [];
    // 开始遍历执行
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(res => {
        result[i] = res;
        flag++;
        if (flag === promises.length) {
          resolve(result)
        }
      }).catch(err => {
        reject(err)
      })
    }
  })  
}

手写Promise.race

Promise.race函数接收的是一个可迭代对象,相当于让这个可迭代对象中的所有promise对象进行赛跑,只要有一个promise对象发生了状态变化,那么直接返回这个promise对象返回的结果。

// 手写promise.race
function myPromiseRace(iterator) {
  // 首先返回的是一个promise对象
  return new Promise((resolve,reject) => {
    for (let item of iterator) {
      Promise.resolve(item).then(res => {
        resolve(item);
      }).catch(err => {
        reject(err);
      })
    }
  })
}
let p1 = new Promise(resolve => {
  setTimeout(resolve, 105, 'p1 done')
})
let p2 = new Promise(resolve => {
  setTimeout(resolve, 100, 'p2 done')
})
myPromiseRace([p1, p2]).then(data => {
  console.log(data); // p2 done
})

手写Promise.finally

Promise.finally的特点

  • 无论成功还是失败,都会执行这个方法
  • 返回的是一个Promise

Promise.finally执行的例子

let p = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve(111);
  },2000)
})
p.then(res => {
  console.log(res);  // 111
}).finally(() => {
  console.log('无论如何这里都会被执行');  // 无论如何这里都会被执行
})

手写Promise.finally(Promise.finally返回的本质上是一个then方法,需要在then方法中执行我们传入的参数,然后返回形参)

Promise.prototype.finally = function(f) {
  return this.then((value) => {
    return Promise.resolve(f()).then(() => value)
  },(err) => {
    return Promise.resolve(f()).then(() => {
      throw err;
    })
  })
}

Promise.all和Promise.race的区别

Promise.all()成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候返回的是最先被reject的值。当Promise.all()的结果是成功的时候,返回结果的数组里边的数据顺序和Promise.all()接收到的promise顺序是一致的。

promise.race表示多个Promise赛跑的意思,里面哪个结果执行的快就返回哪个结果,不管结果本身是成功还是失败,其他Promise代码还会执行,只是不会返回。

Promise.all和Promise.race的应用场景

promise.all()的应用场景

多个异步任务都得到结果时,进行显示的场景

比如,当用户点击按钮时,会弹出一个对话框,这个对话框中的数据来自两个不同的后端接口获取的数据,当用户刚点击的时候,显示的时数据加载中的状态,当这两部分数据都从接口获取到数据的时候,才让数据加载中的状态消失,此时就可以使用Promise.all方法。

Promise.race()的应用场景

提示用户请求超时

比如,当用户点击按钮发送请求的时候,当后端的接口超过我们设定的时间还没有获取到数据的时候,我们就可以提示用户请求超时。

三、Promise是如何解决串行和并行的?

什么是并行?什么是串行?

并行:指的是多个异步请求同时进行。

串行:一个异步请求完成之后再进行下一个请求。

Promise实现并行请求

Promise实现并行请求主要是依靠Promise.all方法和Promise.race方法,我们可以通过手写Promise.all方法或Promise.race方法来实现这一目标。

Promise实现串行请求

Promise实现串行请求主要是借助reduce函数。可以参考我的这篇文章如何控制Promise的串行执行?

// 借助reduce函数来实现Promise的串行执行
const funcArr = [
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(1)},2000)
    })
  },
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(2)},1000)
    })
  },
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(3)},3000)
    })
  },
];
function inOrder(arr) {
  const res = [];
  return new Promise((resolve) => {
    arr.reduce((pre,cur) => {
      return pre.then(cur).then(data => res.push(data))
    },Promise.resolve()).then(data => resolve(res))
  })
}
inOrder(funcArr).then(data => console.log(data))   // [1,2,3]

四、什么是Promise穿透?

所谓的Promise的值穿透指的是.then或者.catch的参数希望是函数,如果传入的不是函数,则可能会发生值穿透。Promise方法通过return传值,没有return就只是相互独立的任务而已。看看下面这个例子的输出可能会更好的帮助我们理解什么是值穿透?

Promise.resolve(1)
  .then(function(){return 2})
  .then(Promise.resolve(3))
  .then(console.log)   // 2

之所以发生了值穿透就是因为第二个then中传入的不是一个函数的形式。

五、使用Promise封装Ajax请求

使用Promise封装Ajax请求的关键步骤,全部在下面的代码中的注释里,详情请看下面的代码。

// 使用Promise封装Ajax请求
const res = new Promise((resolve,reject) => {
  // 1. 创建一个XMLHttpRequest对象
  const xhr = new XMLHttpRequest();
  // 2. 初始化请求方法和URL
  xhr.open('GET','https://api.apiopen.top/getJoke');
  // 3. 发送请求
  xhr.send();
  // 4. 绑定事件,处理响应结果
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      // 这里4代表的就是说服务端返回了全部的结果
      // 如果服务端返回的状态码是2开头的,我们就resolve这个返回的结果,反之则reject对应的状态码
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response)
      } else {
        reject(xhr.status)
      }
    }
  }
})
res.then(function(value) {
  console.log(value);
},function(err) {
  console.log(err);
})

六、Promise有哪些状态?

Promise主要有以下三种状态:

  • pending状态(初始状态)
  • fulfilled状态(已经成功的状态)
  • rejected状态(已经失败的状态)

Promise状态的变化过程

1.从pending到fulfilled状态的切换

resolve前是pending状态,resolve之后是fulfilled状态

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolve前的状态:', p);
    resolve();
    console.log('resolve之后的状态', p);
  })
})

image.png

2.从pending状态到rejected状态

reject前是pending状态,reject之后是rejected状态。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('reject前的状态:', p);
    reject();
    console.log('reject之后的状态', p);
  })
})

image.png

七、将callback改写成Promise

1.传统callback的形式

const fs = require('fs');
fs.readFile('./temp.md',(err,data) => {
  console.log(data.toString());
})

2.将callback改为promise的形式

核心就是通过resolve来获取callback的数据。

const fs = require('fs');
async function myReadFile() {
  let result = await new Promise((resolve,reject) => {
    fs.readFile('./temp.md',(err,data) => {
      resolve(data.toString());
    })
  })
  console.log(result);   // xxxxx
  return result;
}
myReadFile()

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!   

--结束END--

本文标题: 彻底搞懂 javascript的Promise

本文链接: https://lsjlt.com/news/139425.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 彻底搞懂 javascript的Promise
    目录一、为什么要引入PromisePromise解决了什么问题?Promise有哪些具体的使用场景?二、手写Prromise身上的方法手写Promise.all手写Promise.r...
    99+
    2024-04-02
  • 带你彻底搞懂JavaScript的事件流
    目录DOM事件流事件冒泡事件捕获情景一:直接在HTML中绑定事件情景二:[domNode].onclick()方式——DOM0级情景三:[domNode].a...
    99+
    2024-04-02
  • 彻底搞懂 Python 编码
    因为中文的特殊编码,导致 Python2 和 Python3 使用过程中的各种编码问题,如果不清楚其中的关联关系,那么这就一直是个大坑,不是懵逼就还是懵逼,所以就目前碰到的情况彻底梳理下 Python2 和 Python3 中编码的关系和区...
    99+
    2023-01-31
    Python
  • 一文彻底搞懂IO底层原理
    目录一、混乱的 IO 概念二、用户空间和内核空间三、IO模型3.1、BIO(Blocking IO)3.2、“C10K”问题3.3、NIO非阻塞模型3.4、IO多路复用模型3.4.1...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(一)
    目录Java多线程线程的创建线程常用方法线程的终止1.自定义实现线程的终止2.使用Thread的interrupted来中断3.Thraed.interrupted()方法和Thre...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(二)
    目录Java中的锁1.synchronized锁(jvm层的解决方案,也叫监视器锁)2.手动锁Locksynchronized锁synchronized使用场景1.使用synchro...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(三)
    目录Java线程池线程池的优点线程池的6种创建方式创建单个线程池的作用是什么?线程池的第七种创建方式ThreadPoolExecutor的执行方式ThreadPoolExecutor...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(四)
    目录SimpleDateFormat非线程安全问题ThreadLocalThreadLocal的原理ThreadLocal常用方法ThreadLocal的初始化Inheritable...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(五)
    目录单例模式与多线程立即加载/饿汉模式延时加载/懒汉模式饿汉/懒汉对比阻塞队列的实现常见的锁策略乐观锁CASCAS在java中的应用CAS 的ABA问题ABA 问题的解决悲观锁独占锁...
    99+
    2024-04-02
  • 彻底搞懂 PHP 运算符 ?: 和 ??
    文章目录 快速掌握: 短三元运算符 NULL 合并运算符 附上官方文档查阅方式 快速掌握 : 短三元运算符 : 称之为短三元运算符,它是我们熟悉的三元运算符(也叫做条件运算符)的一种特殊写法,也就是省略了三元运算符中间的部分...
    99+
    2023-08-30
    php
  • 一文带你彻底搞懂Vuex
    大家可以思考一下,组件之间的传值有哪些?有父子通讯,兄弟组件通讯......但是传参对于多层嵌套就显得非常繁琐,代码维护也会非常麻烦。因此vuex就是把组件共享状态抽取出来以一个全局单例模式管理,把共享的数据函数放进vuex中,任何组件都可...
    99+
    2022-11-22
    Vue vue3 vue.js VueX
  • 一文带你彻底搞懂JavaScript正则表达式
    目录正则表达式的概述什么是正则表达式正则表达式的作用正则表达式的特点正则表达式在js中的使用创建正则表达式测试正则表达式 test正则表达式中的特殊字符正则表达式的组成边界符字符类量...
    99+
    2024-04-02
  • 一文彻底搞懂Kotlin中的协程
    产生背景 为了解决异步线程产生的回调地狱 //传统回调方式 api.login(phone,psd).enquene(new Callback<User>(){ ...
    99+
    2024-04-02
  • 如何彻底搞懂jdk8线程池
    这篇文章将为大家详细讲解有关如何彻底搞懂jdk8线程池,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。顶层设计,定义执行接口Interface Executor(){ &n...
    99+
    2023-06-25
  • 彻底搞懂java并发ThreadPoolExecutor使用
    目录前言正文一. 线程池的简单原理二. 线程池的创建三. 线程池执行任务1. 执行无返回值任务2. 执行有返回值任务3. 执行有返回值任务时抛出错误4. ThreadPoolExec...
    99+
    2023-02-28
    java并发ThreadPoolExecutor java 并发
  • 彻底搞懂Docker镜像分层的实现
    目录创建测试镜像查看镜像使用docker inspect使用docker history镜像分层图镜像分层的好处镜像分层的实现Copy-on-write策略创建测试镜像 我们创建一个...
    99+
    2024-04-02
  • 一文让你彻底搞懂AQS(通俗易懂的AQS)
    一文让你彻底搞懂AQS(通俗易懂的AQS) 一、什么是AQS AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Se...
    99+
    2023-09-04
    java 开发语言
  • Java基础:彻底搞懂java多线程
    目录进程与线程使用多线程的优势线程的状态创建线程线程中断总结进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可...
    99+
    2024-04-02
  • 一文彻底搞懂Python中__str__和__repr__
    __str__和__repr__有什么异同字符串的表示形式我们都知道,Python的内置函数 repr()​ 能够把对象用字符串的形式表达出来,方便我们辨认。这就是“字符串表示形式”。repr()​ 就是通过 __repr__​ 这个特殊方...
    99+
    2023-05-14
    Python 函数 内置
  • 彻底搞懂 python 中文乱码问题
    前言 曾几何时 Python 中文乱码的问题困扰了我很多很多年,每次出现中文乱码都要去网上搜索答案,虽然解决了当时遇到的问题但下次出现乱码的时候又会懵逼,究其原因还是知其然不知其所以然。现在有的小伙伴为了躲避中文乱码的问题甚至代码...
    99+
    2023-01-31
    乱码 中文 python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作