浅谈对Promise的理解以及在工作中的应用

news2024/11/27 1:04:06

浅谈对Promise的理解以及在工作中的应用

  • Promise的概念
    • 背景知识
    • JavaScript的同步和异步
    • JavaScript事件循环
    • 回调函数进行异步操作
    • 解决方案:Promise
  • Promise 在工作中的运用
    • 创建Promise
    • Promise封装AJAX
    • Promise链式操作
    • Promise.all()
    • Promise.race()
    • async和await
  • 总结

Promise的概念

在开始讲解Promise前,我们先大致了解一下js的运行机制以及多个任务是怎么运作的。

背景知识

众所周知,JavaScript是一门单线程语言,也就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着,这样会造成浏览器处于假死状态,严重影响用户体验。为了解决这个问题,js引入了异步的概念,在执行任务时挂起处于等待中的任务,先运行排在后面的任务。等到刚才挂起的任务返回了结果,再回过头,把挂起的任务继续执行下去。于是,js将所有的任务分成了两种,一种是同步任务synchronous),另一种是异步任务asynchronous)。

JavaScript的同步和异步

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行;
除了广义的同步任务和异步任务,对任务更精细的定义:

宏任务(macro-task):包括整体代码script,setTimeout,setInterval
微任务(micro-task):Promise,process.nextTick

宏任务中有微任务,一定要将宏任务中的微任务执行完毕,再去执行下一个宏任务
那究竟什么是任务队列呢,同步任务和异步任务又是怎么执行的,这就要引入js的运行机制:事件循环(event loop)

JavaScript事件循环

在这里插入图片描述
js事件循环运作机制如下:

  1. 同步任务和异步任务分别进入不同的“场所”,同步任务进入主线程,异步任务进入Event Table (事件表)并注册函数
  2. 指定的事件完成后,Event Table (事件表)会将这个函数移入到Event Queue (事件队列)
  3. 当主线程的任务完成后,会检查Event Queue (事件队列),如果有任务就全部执行,如果没有就进入下一个宏任务
  4. 这个过程会不断的重复,这就叫事件循环

回调函数进行异步操作

和同步操作不同,异步操作是不会立即返回结果的(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?

通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)

// 下载
function download(callback){
    // 模拟异步操作
    setTimeout(function(){
        // 调用回调函数
        callback('下载完成');
    }, 1000);
}

function callback(value){
    // 下载完成的处理
    console.log(value);
}

download(callback);

// 这段代码将在1秒后在控制台打印“下载完成”

但假如callback函数同样是个异步函数,且callback里又嵌入了callback呢? 例如需求是等待第一个文件下载完成后,再下载第二个文件,等待第二个文件下载完成后,再下载第三个文件…,这样的话,上面这种方法就不可取了,因为会产生很多的函数嵌套,嵌套太深容易引发回调地狱(指的是回调函数里嵌套回调函数,使得代码可读性非常差,容易陷于无止尽的循环)

     //回调地狱
    setTimeout(function () {  //第一层
        console.log('张三');//等3秒打印张三在执行下一个回调函数
        setTimeout(function () {  //第二层
            console.log('李四');//等2秒打印李四在执行下一个回调函数
            setTimeout(function () {   //第三层
                console.log('王五');//等一秒打印王五
            }, 1000)
        }, 2000)
    }, 3000)

解决方案:Promise

所以为了回调地狱的问题,promise方案应运而生,它是对回调方法的一种封装,是用来处理异步操作的,可以让我们写异步调用的时候写起来更加优雅,更加美观。
以下是promise的一些特点

  1. Promise 对象代表一个异步操作,对象的状态不受外界影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)、settled (结束)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,且一旦状态改变就不可再变
  2. Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据,其中then方法的两个参数是resolve(成功回调),reject(失败回调)。异步任务执行成功时调用resolve函数返回结果,反之调用reject,根据不同的任务,由开发者来决定resolve和reject在函数体内的位置
  3. then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then/catch方法,链式调用可以保证代码的执行顺序
    promise解决刚才的地狱回调问题:
// promise解决方式
function fn(str) {
    var promise = new Promise(function (resolve, reject) { //resolve是成功的方法  reject是失败的方法  
        //处理异步任务
        var flag = true;
        setTimeout(function () {
            if (flag) {
                resolve(str)
            }
            else {
                reject('失败')
            }
        })
    })
    return promise;
}
 
fn('张三')
    .then((res) => { //then是成功执行的方法 返回的还是一个promise对象
        console.log(res);//打印张三  res是结果
        return fn('李四');
    })
    .then((res) => {
        console.log(res);
        return fn('王五')
    })
    .then((res) => {
        console.log(res);
    })
    .catch((res) => { //catch是失败执行的方法
        console.log(res);
    })

Promise 在工作中的运用

创建Promise

promise构造器只接收一个参数,该参数被称为执行器(executor)的函数。该函数会被传递两个参数(方法),一个叫做resolve,另一个叫做reject。

resolve函数在成功时调用,reject函数在失败时被调用。并且resolve和reject只能被使用一次,如果之后还有resolve和reject也不会被执行了,有点儿类似于return,但是不同点在于,其他代码还会被照常执行。

new Promise((resolve, reject)=> {
  resolve('我是第一次调用resolve');
  console.log('我是其他代码');
  resolve('我是第二次调用resolve'); // 不在起作用
  reject('我来调用reject'); // 不在起作用
})

也可以直接使用Promise.resolve或者Promise.reject来创建成功或者失败的Promise

let p1 = Promise.resolve('我是成功的Promise'),
    p2 = Promise.reject('我是失败的Promise');

Promise封装AJAX

var ajax = function(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readystate == 4 && xhr.status == 200) {
                resolve(xhr.responseText)
            } else if (xhr.readystate == 4 && xhr.status !== 200) {
                reject(xhr.statusText)
            }
        }
    })
}
ajax.then(console.log(xhr.responseText)); //打印出返回的数据

Promise链式操作

由于Promise的then 方法始终返回一个 Promise 对象, 所以Promise 可以一直调用 then 方法,从而实现链式调用(解决地狱回调)。不管 new Promise 创建出来的执行状态是成功 / 失败,只要在 then / catch方法中通过 return 返回一个结果,不管这个值是 Promise 对象还是普通值,都可以通过链式调用的 .then 方法中获取到这个值,因为 promise.then 方法会默认在返回值的外层包裹一层 Promise 对象,这样才可以实现 Promise 一直通过 .then 的方式去链式调用

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
    setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
    setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
    setTimeout(()=> {
        resolve(name);
    }, 3000)
})

//方式一:链式操作返回promise对象
p1.then((res) => {
    console.log(res+'第一个出场');
    return p2
}).then((res) => {
    console.log(res+'第二个出场');
    return p3
}).then((res) => {
    console.log(res+'第三个出场');
})

//链式操作返回promise对象输出结果
张三第一个出场
李四第二个出场
王五第三个出场


//方式二:链式操作返回普通值
p1.then((res) => {
    console.log('我是'+res);
    return res
}).then((res) => {
    console.log('我是'+res+'的儿子');
    return res
}).then((res) => {
    console.log('我是'+res+'的孙子');
})

//链式操作返回普通值输出结果
张三第一个出场
李四第二个出场
王五第三个出场

Promise.all()

这个方法返回一个新的promise对象,该promise对象在参数对象promises里所有的promise对象都成功的时候才会触发成功,一旦有任何一个promises里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含promises里所有promise返回值的数组作为成功回调的返回值,顺序跟promises的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
简而言之,all方法会将传入的异步操作并行执行,等到它们都执行完后才会进到then方法,从时间上来看取决于最后一个异步任务执行完成的时间

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
        setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
        setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
        setTimeout(()=> {
        resolve(name);
    }, 3000)
})

// promise.all用法
Promise.all([p1,p2,p3]).then((res) => {
    console.log(res+'都已到达终点');
})

// promise.all输出结果
张三,李四,王五都已到达终点

Promise.race()

race就是赛跑的意思,谁先出结果就由谁决定,采用第一个 promise 的值作为它的值,当promises参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
        setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
        setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
        setTimeout(()=> {
        resolve(name);
    }, 3000)
})

// promise.race用法
Promise.race([p1,p2,p3]).then((res) => {
    console.log(res+'第一个到达终点');
})

// promise.all输出结果
李四第一个到达终点

async和await

async 是“异步”的简写,而 await 可以认为是 async wait(等待) 的简写。
所以应该很好理解 async 用于申明一个 function 是异步的,返回的是一个 Promise 对象.
而 await 用于等待一个异步方法执行完成,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值


//基本用法的async函数
let asyncFun = async function(){
    return 1
}
console.log(asyncFun())
//会返回一个promise对象

//使用场景
//摇色子方法
function dice(){
    return new Promise((resolve,reject)=>{
        let sino = parseInt(Math.random()*6+1)  //生成一个1~6之间的随机小数
        setTimeout(()=>{
            resolve(sino)
        },2000)
    })
}
//异步方法
 async function text(){
     let n= await dice()
      //await 关键字后面调用摇色子方法执行完毕之后,才进行变量赋值
     console.log("摇出来"+n)  //最后打印出摇出来的数
 }
text()

//输出结果
Promise { 1 }
摇出来5

总结

以上就是我个人关于Promise的理解以及在工作中的应用,总的来说Promise在日常开发工作中的使用还是比较多的,他最大的用途在于让多个异步的任务按照我们想要的方式去执行。但想要彻底理解promise的运行方式及原理,还需要了解js引擎的运行逻辑,任务队列、宏任务微任务等。上文中如果有错误的地方欢迎指正,大家共同进步,越秃越强!

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

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

相关文章

轻松转换文档:antennahouse/Office Server Document Converter

关于 Office Server 文档转换器 (OSDC)破解版 无需 Microsoft Office 或 Adob​​e 软件即可快速准确地转换文档。 Office Server 文档转换器 (OSDC) 会将您在 Microsoft Office(Word、Excel、PowerPoint)中创建的重要文档转换为高质量的 PDF 或图像格式…

国内32位MCU在电机控制上的应用方案

电机(Electric machinery,俗称“马达”)是依据电磁感应定律,实现电能转换或传递的一种电磁装置,其主要作用是产生驱动转矩,为用电器或各类机械提供动力。电机作为工业世界的动力之源,几乎用于所…

ThinkPHP 6.1 模板篇之布局与继承

本文主要讲述ThinkPHP 6.1版本模板几种布局的方法和如何实现继承, 可以与《ThinkPHP 6.1 模板篇之文件加载》结合来看。 模板布局 布局方式有两种可以实现。 布局方法1 开启配置 默认情况下,不支持模版布局功能,需要在配置文件中开启&…

如何快速通过PMP考试?

我建议准备的最短时间至少一个月,我用了一个半月,我每天集中精力备考大约4个小时,大家可以根据自己的专注力的长短去调节每天的备考时间。 准备5月的,还没备考的,现在开始也来得及。5月没有报名的可以准备8月的&#…

【Linux系统编程】05:多进程

多进程 OVERVIEW多进程一、进程创建1.创建1个子进程2.创建多个子进程二、进程控制1.进程结束2.进程等待3.子进程操作14.子进程操作2三、进程体系1.守护进程2.进程调度程序:一种已经编译好的、存在磁盘中的二进制文件(脚本为普通文件)。进程&a…

超图iServer扩展开发记录Restlet 3

HTTP 请求在达到 REST 应用对象,交给资源实现类处理的时候,先要解析 HTTP 请求中的参数,然后才会进入业务逻辑进行处理。参数解析的工作由参数解析器(Decoder)进行,即可以实现将请求参数转换为 Java 对象。…

qt tcp通讯

TCP 协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。tcp服务端使用QTcpServer、QTcpSocket。tcp客户端使用QTcpSocket1.在工程文件(工程文件.pro)中的第一行添加network 如QT core gui …

WeSpeaker支持C++部署链路

WeSpeaker正式更新C部署链路,推理引擎使用OnnxRuntime,支持从语音中提取Speaker Embedding信息,代码详见WeSpeaker/runtime[1]。 Libtorch和onnx的选择? Speaker Embedding提取任务流程简单,并且声纹模型(如ResNet\E…

前端js学习

1. js入门 1.1 js是弱类型语言 1.2 js使用方式 1.2.1 在script中写 1.2.2 引入js文件 1.2.3 优先级 1.3 js查错方式 1.4 js变量定义 1.4 js数据类型 数据类型英文表示示例数值类型number1.1 1字符串类型string‘a’ ‘abc’ “abc”对象类型object布尔类型booleannumber函数…

包教包会的Node.js

一、简介 1、什么是Node.js 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。 2、Node.js有什么用 如果你是一个前…

风起|微软突发声明:始终严格保护并捍卫用户隐私

开放隐私计算 3 月 9 日消息,微软中国今天发布了声明,针对日前国内某些自媒体传播的有关个人用户使用微软消费类产品和服务的误解,特做了相关说明。微软表示,微软始终严格遵守个人隐私保护与数据安全等方面的各项法律法规。微软提…

深眸科技突破革新机器视觉技术,加速实现工业自动化与智能化发展

随着现代生活水平的不断提高,人们对产品的品质需求持续提升,且在智能制造这一大环境下,多数制造企业积极转型,寻求更高效的检测方式。而机器视觉及相关技术的发展,让多数公司通过创新机器视觉应用产品,以及…

面试必会-MySQL篇

1. Mysql查询语句的书写顺序Select [distinct ] <字段名称>from 表1 [ <join类型> join 表2 on <join条件> ]where <where条件>group by <字段>having <having条件>order by <排序字段>limit <起始偏移量,行数>2. Mysql查询语…

vue3使用nextTick

发现nextTick必须放在修改一个响应式数据之后&#xff0c;才会在onUpdated之后被调用&#xff0c;如果nextTick是放在所有对响应式数据修改之前&#xff0c;则nextTick里面的回调函数会在onBeforeUpdate方法执行前就被调用了。可是nextTick必须等到onUpdated执行完成之后执行&a…

Android代码重构系列-02-使用Kotlin协程实现一个支持任务编排的轻量级启动器

前言虽然本文的主题是启动器&#xff0c;但是笔者不打算去写怎么做启动优化&#xff0c;以及怎么实现一个完美的启动器。关于开源的第三方Android启动器已经有很多优秀的轮子了&#xff0c;比如阿里巴巴的alpha&#xff0c;参考 alpha 并改进其部分细节的Anchors&#xff0c;St…

Mybatis框架源码笔记(七)之Mybatis中类型转换模块(TypeHandler)解析

1、JDBC的基本操作回顾 这里使用伪代码概括一下流程: 对应数据库版本的驱动包自行下载加载驱动类 (Class.forName("com.mysql.cj.jdbc.Driver"))创建Connection连接: conn DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnico…

最新消息:2023年软考高项教材改版!

最新通知&#xff1a;从2023年上半年软考开始信息系统项目管理师考试将依据新版考试大纲进行。 给备考高项的朋友的一些建议&#xff1a; 备考资源&#xff1a; 【腾讯文档】软考各科资料分享 https://docs.qq.com/doc/DTVN1SWtFZHdicUNp 复习方法&#xff1a; 选择题 选择题…

ChatGPT,乌合之众的疯狂

最近ChatGPT有多火爆就不用我说了。公司里&#xff0c;从CEO到技术人员&#xff0c;乃至于门口的保安、食堂的大婶&#xff0c;没有一个不会聊两句ChatGPT的。连我20年未见的小学同学、三线城市警官&#xff0c;都问我这东西能不能给领导写汇报材料。 用不了多久&#xff0c;家…

颠覆推特VS改造推特:什么是去中心化社交的正确姿势?

去年&#xff0c;“钢铁侠”伊隆马斯克收购了全球最大的社交媒体之一——推特。推特成立于2006年&#xff0c;是一个“公民广场”&#xff0c;允许大家公开发表观点和内容。用户可以关注自己喜欢的账号&#xff0c;也可以点赞转发评论他人的推文&#xff0c;中国的微博便是照搬…

【halcon】轮廓拟合相关算子

涉及函数 edges_sub_pix 寻找边缘 edges_sub_pix (Image, Edges, canny, 1, 10, 20) 后面三个参数&#xff0c;越小&#xff0c;找到的细节越多。这个是对应录波器为canny时。 canny滤波器用的最多。 segment_contours_xld 将连续的轮廓进行分段&#xff0c;按圆弧或者执…