面试问到 Promise,这样回答最完美了

2023-05-16

promise是什么?

 

Promise是异步编程的一种解决方案,比传统的回调函数和事件更合理和强大。

所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事情(通常是一个异步操作)。从语法上说,Promise是一个对象,从他可以获取异步操作的消息。

特点:

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(以失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来。

  • 一旦状态改变,就不会再变,任何时候都是可以得到这个结果的。Promise对象的状态改变只有两种可能:*从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就会凝固,不会再变了。再对Promise对象添加回调函数也会立即得到这个结果。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来。

缺点:

首先无法取消Promise,一旦新建他就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部跑出的错误无法反应到外部。当pending的时候,无法知道进展到了哪一步。

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) {    if(success) {        resolve(value)    } else {        reject(error)    }})

 

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从"未完成"变成"成功"。(即从pending变为resolved)。在异步操作成功的时候调用,并将异步操作结果作为参数传递出去;

reject函数的作用是,将promise对象的状态从"未完成"变成"失败"(即从pending变为rejected)。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成后,可以用then方法分别指定resolve状态和rejected状态的回调函数。

promise.then(function(value) {      }, function(error) {})

 

then方法可以接受两个回调函数作为参数,

第一个回调函数是promise对象的状态变为resolved的时候调用,

第二个回调函数是promise对象的状态变为rejected时调用。

其中第二个函数是可选的,不一定需要提供。

这两个函数都接受Promise对象传出的值作为参数。

function timeout(ms) {    return new Promise((resolve, reject) => {        setTimeout(resolve, ms, 'done')//setTimeout 传参    })}timeout(100).then((value) => {    console.log(value)//done})

 

上面代码中,timeout方法返回一个Promise实例,表示一段时间后才会发生的结果。

过了指定的时间以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

Promise新建后就会立即执行。

let promise = new Promise(function(resolve, reject) {    console.log('Promise')    resolve()})promise.then(function() {    console.log('resolved')})console.log('Hi')//Promise//Hi//resolved

上面代码中,Promise新建后立即执行,所以首先输出的是Promise,然后then方法指定回调函数,将在当前脚本所有同步任务执行完成后才会执行,所以resolved最后输出。

如下是一个异步加载图片的例子:

function loadImageAsync(url) {    return new Promise(function(resolve, reject) {        const image = new Image()             image.onload = function() {            resolve(image)        }           image.onerror = function() {            reject(new Error('count not load...'))        }        image.src = url    })}

上面代码中,使用Promise包装一个图片加载的异步操作,如果加载成功就调用resolve方法, 否则就调用rejected方法。

1. Promise.prototype.then()

Promise实例具有then方法,也就是说then方法时定义在原型对象上的。

它的作用是为Promise实例添加状态改变时的回调函数。

前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数(可选)。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)因此可以采用链式写法,即then方法后面再调用另一个then方法。

采用链式的then可以指定一组按照次序调用的回调函数。这时,前一个回调函数可能返回一个还是Promise对象(即有异步操作),这时候一个回调函数就会等该Promise对象的状态发生变化,才会被调用

getJSON('/post/1.json').then(function(post) {    return getJSON(post.commentURL)}).then(function funcA() {    console.log("resolved:", comments)}, function funcB(err) {    console.log("rejected:", err)})

上面代码中,第一个then方法指定的回调函数,返回的是一个Promise对象。这时,第二个方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA, 如果状态变为rejected,就调用funcB.

2. Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数

getJSON('/post/1.json').then(function(posts) {    //...}).catch(function() {    console.log('发生错误', error)})

上面代码中,getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数。另外,then方法指定的回调函数,如果运行抛出错误,也会被catch方法捕获。

p.then(val => console.log('fulfilled:', val)) .catch(err => console.log('rejected', err))
//等同于p.then(val => console.log('fulfilled:', val)) .then(null, err => {console.log('rejected:', err)})

如果Promise状态以及变成resolved,再抛出错误是无效的。因为Promise的状态一旦改变,就永久保持该状态,不会再变了。

Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也就是说错误总会被下一个catch语句捕获。

3. Promise.prototype.finally()

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。

4.Pomise.all的使用

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中  promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

具体代码如下:

let t1 = new Promise((resolve,reject)=>{    resolve("t1-success")})let t2 = new Promise((resolve,reject)=>{    resolve("t2-success")})let t3 =Promise.reject("t3-error");Promise.all([t1,t2,t3]).then(res=>{    console.log(res)}).catch(error=>{    console.log(error)})//打印出来是t3-error

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

let request = (time,id) => {    return new Promise((resolve, reject) => {      setTimeout(() => {        resolve(`第${id}个请求${time / 1000}秒`)      }, time)    })  }    let p1 = request(3000,1)  let p2 = request(2000,2)    Promise.all([p1, p2]).then((result) => {    console.log(result)       // [ '第1个请求3秒', '第2个请求2秒' ]  }).catch((error) => {    console.log(error)  })

需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

5、Promise.race的使用

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

let f1 = new Promise((resolve, reject) => {    setTimeout(() => {      resolve('success')    },1000)  })    let f2 = new Promise((resolve, reject) => {    setTimeout(() => {      reject('failed')    }, 500)  })    Promise.race([f1, f2]).then((result) => {    console.log(result)  }).catch((error) => {    console.log(error)  // 打开的是 'failed'  })

原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。

举例:超时取消

我们来看一下如何使用Promise.race来实现超时机制。

当然XHR有一个 timeout 属性,使用该属性也可以简单实现超时功能,但是为了能支持多个XHR同时超时或者其他功能,我们采用了容易理解的异步方式在XHR中通过超时来实现取消正在进行中的操作。

1. 让Promise等待指定时间

首先我们来看一下如何在Promise中实现超时。

所谓超时就是要在经过一定时间后进行某些操作,使用 setTimeout 的话很好理解。

首先我们来串讲一个单纯的在Promise中调用 setTimeout 的函数。

//delayPromise.jsfunction delayPromise(ms) {    return new Promise(function (resolve) {        setTimeout(resolve, ms);    });}

delayPromise(ms) 返回一个在经过了参数指定的毫秒数后进行onFulfilled操作的promise对象,这和直接使用 setTimeout 函数比较起来只是编码上略有不同,如下所示。

setTimeout(function () {    alert("已经过了100ms!");}, 100);// == 几乎同样的操作delayPromise(100).then(function () {    alert("已经过了100ms!");});

在这里 promise对象 这个概念非常重要,请切记。

2. Promise.race中的超时

我们可以将刚才的 delayPromise 和其它promise对象一起放到 Promise.race 中来是实现简单的超时机制。

//simple-timeout-promise.jsfunction delayPromise(ms) {    return new Promise(function (resolve) {        setTimeout(resolve, ms);    });}function timeoutPromise(promise, ms) {    var timeout = delayPromise(ms).then(function () {            throw new Error('Operation timed out after ' + ms + ' ms');        });    return Promise.race([promise, timeout]);}

 

函数 timeoutPromise(比较对象promise, ms) 接收两个参数,第一个是需要使用超时机制的promise对象,第二个参数是超时时间,它返回一个由 Promise.race 创建的相互竞争的promise对象。

之后我们就可以使用 timeoutPromise 编写下面这样的具有超时机制的代码了。

function delayPromise(ms) {    return new Promise(function (resolve) {        setTimeout(resolve, ms);    });}function timeoutPromise(promise, ms) {    var timeout = delayPromise(ms).then(function () {            throw new Error('Operation timed out after ' + ms + ' ms');        });    return Promise.race([promise, timeout]);}// 运行示例var taskPromise = new Promise(function(resolve){    // 随便一些什么处理    var delay = Math.random() * 2000;    setTimeout(function(){        resolve(delay + "ms");    }, delay);});timeoutPromise(taskPromise, 1000).then(function(value){    console.log("taskPromise在规定时间内结束 : " + value);}).catch(function(error){    console.log("发生超时", error);});

虽然在发生超时的时候抛出了异常,但是这样的话我们就不能区分这个异常到底是_普通的错误_还是_超时错误_了。

为了能区分这个 Error 对象的类型,我们再来定义一个Error 对象的子类 TimeoutError。

扩展知识:定制Error对象

Error 对象是ECMAScript的内建(build in)对象。

但是由于stack trace等原因我们不能完美的创建一个继承自 Error 的类,不过在这里我们的目的只是为了和Error有所区别,我们将创建一个 TimeoutError 类来实现我们的目的。

在ECMAScript6中可以使用 class 语法来定义类之间的继承关系。

class MyError extends Error{    // 继承了Error类的对象}

为了让我们的 TimeoutError 能支持类似 error instanceof TimeoutError 的使用方法,我们还需要进行如下工作。

//TimeoutError.jsfunction copyOwnFrom(target, source) {    Object.getOwnPropertyNames(source).forEach(function (propName) {        Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));    });    return target;}function TimeoutError() {    var superInstance = Error.apply(null, arguments);    copyOwnFrom(this, superInstance);}TimeoutError.prototype = Object.create(Error.prototype);TimeoutError.prototype.constructor = TimeoutError;

我们定义了 TimeoutError 类和构造函数,这个类继承了Error的prototype。

它的使用方法和普通的 Error 对象一样,使用 throw 语句即可,如下所示。

var promise = new Promise(function(){throw TimeoutError("timeout");});promise.catch(function(error){ console.log(error instanceof TimeoutError);// true});

有了这个 TimeoutError 对象,我们就能很容易区分捕获的到底是因为超时而导致的错误,还是其他原因导致的Error对象了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试问到 Promise,这样回答最完美了 的相关文章

随机推荐

  • 【Gitee】关于远程代码的拉取、修改和重新上传

    一 关于Forked forked的意思是从别人的代码库中复制一份到你自己的代码库 xff0c 与普通的复制不同 xff0c fork包含了原有库中的所有提交记录 xff0c fork后这个代码库是完全独立的 xff0c 属于你自己 xff
  • 基于视频采集卡驱动的错误修改CX26828

    基于视频采集卡驱动的错误修改CX26828 1 设置root密码 command xff1a sudo passwd root 2 查看系统状态 输入命令 xff1a lsmod root 64 ubuntu home yu lsmod i
  • Sqlite远程连接数据

    Sqlite远程连接数据 1 连接本地数据库 QSqlDatabase db 61 QSqlDatabase addDatabase 34 QSQLITE 34 db setDatabaseName 34 personnel db 34 d
  • 基于Tiny210开发板视频显示

    基于Tiny210开发板视频显示 1 写基于V4L2编程 61 61 61 61 61 61 61 61 videodevice h文件 61 61 61 61 61 61 61 61 61 ifndef VIDEODEVICE H def
  • 纯C++去雾算法

    去雾算法 前言 xff1a 经过不断的改进研究 xff0c 该算法终于稳定 xff0c 高效的问世了 xff01 经过研究使该算法适应大雾环境 xff0c 对该算法的内存优化 xff0c 能够实时的高效的运行 一 实时视频 xff1a 二
  • 致院长的一封信

    致院长的一封信 行了一路 xff0c 梦了一路 xff0c 念了一路 xff0c 伤了一路 xff0c 青春的此岸 xff0c 我眉心微蹙 xff0c 聆听清风诉说着经年的过往 xff1b 走了一路 xff0c 赏了一路 xff0c 听了一
  • LINUX字符设备驱动程序实例

    我是通过UBUNTU10 10测试该驱动程序的 xff0c 系统内核为linux 2 6 35 22 可使用uname r 命令来查看当前内核的版本号 下载安装LINUX内核 xff0c 需要下载和本机一样版本的内核源码 1 xff0c 安
  • c++多态总结

    今天来总结一些c 43 43 中 xff0c 有关多态的知识 多态 xff1a 多态可以简单地概括为 一个接口 xff0c 多种方法 xff0c 程序在运行时才决定调用的函数 xff0c 它是面向对象编程领域的核心概念 接下来 xff0c
  • 600词汇过四级

    600 词汇过四级 A 1 abandon vt 抛弃 xff0c 放弃 abandon oneself to despair 陷于绝望 xff1b abandon oneself to failures 自暴自弃 2 abrupt adj
  • 华清远见c语言学习笔记四

    test c Created on Jun 29 2012 Author 孙旭 华清远见实验室 1 include lt stdio h gt include lt stdlib h gt include lt string h gt in
  • 华清远见c语言学习笔记六

    test c Created on Jun 29 2012 Author 孙旭 华清远见实验室 1 include lt stdio h gt 找出一个字符串中的数字 include lt string h gt int main char
  • c++示例代码-友元

    一 友元函数 include lt iostream h gt include lt math h gt class Point Point类声明 public 外部接口 Point int xx 61 0 int yy 61 0 X 61
  • C/C++空指针总结

    在C中 在C中 xff0c 使用NULL表示空指针 xff0c 实际上 xff0c NULL被定义为 xff1a define NULL void 0 其中 void 0 表示对 0 进行强制转换 xff0c 转换为一个void类型的指针
  • Windows11镜像下载及安装

    现在微软已正式推出Windows 11预览版系统 xff0c 目前最新系统版本是22000 71 xff0c 正式版可能会在10月份推出 xff0c 届时拥有正版Windows 10系统的就可以通过Windows Update工具免费在线升
  • ROS2安装及基础知识介绍

    ros介绍 ROS xff08 Robot Operating System xff09 是一个开源的机器人操作系统 xff0c ROS系统是由大量节点组成 xff0c 其中任何一个节点都可以通过发布 订阅的方式与其他节点进行通信 举个栗子
  • Ubuntu Gnome GTK程序开机启动

    启动图标准备 GTK程序 开机启动需要首先制作desktop文件 例如 myapp autostart desktop 如下 xff1a Desktop Entry Encoding 61 UTF 8 Name 61 App Comment
  • 学习记录

    最近打算用visio画个流程图 xff0c 看看会不会遇到什么坑 1 最近打算用visio画几个流程图 xff0c 遇到什么问题在记录一番 2 分析学习一下系统权限的分配 xff0c 做到可以配置权限 xff0c 灵活一些 用户 gt 角色
  • 计算机专业学生,大三了找技术岗,怎么写一份好简历?内附269份简历模板

    计算机专业学生 xff0c 大三了找技术岗 xff0c 怎么写一份好简历 xff1f 内附269份简历模板 大家好 xff0c 我是好好学习 xff0c 天天编程的博主 xff0c 一个每天在互联网上种菜和砍柴的程序员 因为疫情的影响 xf
  • Digest Authentication 摘要认证(转载)

    原文 Digest Authentication 摘要认证 weixin 34007906的博客 CSDN博客 摘要 式认证 xff08 Digest authentication xff09 是一个简单的认证机制 xff0c 最初是为HT
  • 面试问到 Promise,这样回答最完美了

    promise是什么 xff1f Promise是异步编程的一种解决方案 xff0c 比传统的回调函数和事件更合理和强大 所谓Promise xff0c 简单来说就是一个容器 xff0c 里面保存着某个未来才会结束的事情 xff08 通常是