Promise 深度学习

news/2024/7/5 3:41:26

文章目录

    • Promise 由来
    • Promise的用法
    • reject的用法
      • finally
    • all的用法
    • race的用法
    • 总结

Promise 由来

我们处理异步函数最普通的方法是这样的,等待上一次请求结束再执行下一步操作:

// 一般以定时器来模拟一次请求
setTimeout(() => {
  console.log("first");
  // 处理的内容 或者 下一次请求
  // TODO
}, 1000);

这是两次的请求,看着是比较简单,这么写好像也没什么影响,但如果TODO后面还有多个请求时:

// 用多个定时器来模拟多个请求
setTimeout(() => {
  console.log("第一次处理");
  setTimeout(() => {
    console.log("第二次处理");
    setTimeout(() => {
      console.log("第三次处理");
        setTimeout(() => {
          console.log("第四次处理");
          // TODO
        }, 4000)
    }, 3000)
  }, 2000)
}, 1000);

回调函数中嵌套回调函数,就是回调地狱,这种写法仔细看我们肯定是能看得出来的,但是无限嵌套之后,代码的可读性非常差,日常维护也是很繁琐的事情。为了更好的处理嵌套格式的代码,我们就可以使用Promise。这当然也是ES6(ES2015)中最重要的特性之一,开发中经常使用,面试的时候也经常问。

Promise的用法

我们可以先打印看看,Promise长什么样:

console.dir(Promise);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMw6cXvW-1687913685665)(C:\Users\zy3\Desktop\study\drop-of-water\water\web\Vue2.0\2023\img\打印出的Promise.png)]

显而易见的Promise是一个构造函数,本身带着 all、race、reject、resolve等方法,prototype原型上也带着 catch、then等方法。new Promise:

// 一般Promise new的时候都喜欢用一个函数包一下,这里还是使用定时器代替请求
const p = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    console.log("时间到了");
   	resolve("成功操作")
  }, 1000)
});
p.then(res => {
  console.log(res);
});

// 执行后的结果为: 
// 时间到了
// 成功操作

Promise的构造函数接收了一个参数,是一个函数,并且函数中传入两个参数 resolve、reject,分别是异步操作执行成功后的回调函数和异步操作执行失败后的回调函数(这么描述并不是正确的,实际上是 resolve将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected,状态操作是不可逆的,Promise一开始的状态是pending初始态,状态改变方式也就两种)。

上面代码,定时器一秒之后输出“时间到了”,并且调用resolve方法。在异步任务执行完之后,再打印“成功操作”,这就是Promise的作用:将原来的回调写法分离出来,在异步操作执行完之后,用链式调用的写法执行回调函数

这里只是最简单的Promise,如果要改造前面写的多个定时器请求,我们可以这样写:

// Promise优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("第一次请求");
    resolve();
  }, 1000)
})

// 链式操作的用法
p1
  .then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("第二次请求");
        resolve()
      }, 2000)
    })
  })
  .then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("第三次请求");
        resolve();
      }, 3000)
    })
  })
  .then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("第四次请求");
      })
    }, 4000)
  })

// 换一种写法,执行结果一样 将函数定义一下
function p1() {
  const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第一次请求");
      resolve(1);
    }, 1000)
  })
  return p;
}

function p2() {
  const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第二次请求");
      resolve(2);
    }, 1000)
  })
  return p;
}

function p3() {
  const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第三次请求");
      resolve(3);
    })
  })
  return p;
}

// p1只是返回函数,没有调用,需要在p1后面添加一个 ()
p1()
  .then(res => {
    console.log(res);
    return p2();
  })
  .then(res => {
    console.log(res);
    return p3();
  })
  .then(res => {
    console.log(res);
  })
// 这样就没有回调地狱了

reject的用法

上面的代码都只用了resolve,我们还没用过reject。事实上前面的代码假设都是成功的,还没有失败的情况:

// 一个在定时器中的随机生成[0,10)随机数的函数
function getNumFn() {
 const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      let num = Math.ceil(Math.random() * 10); // 随机生成1-10(不包含)的随机数
      if (num <= 5) {
        // 模拟成功时的操作
        resolve("数字小于5", num);
      } else {
        // 模拟失败时的操作
        reject("数字大于5", num);
      }
    }, 1000)
 });
 return p;
};

getNumFn()
  .then(res => {
    console.log("resolve", res);
  })
  .catch(err => {
    console.log("reject", err);
  })

我们可以看出数字小于5的时候会执行then里的内容,数字大于5时会执行catch中的操作,一般resolve对应then,reject对应catch;但其中then是一个卷王,它可以接收两个参数,一个对应resolve的回调,第二个对应的reject的回调(曾经有个面试官问过我then能接收几个参数,啥也不会),上面的调用也可以写成这样:

getNumFn().then(
  res => {
    console.log(res);
  },
  err => {
    console.log(err);
  }
)

这时就简单达到跟catch一样的效果了。但实际上,在执行resolve的回调时,如果抛出异常了/代码报错了,那么就会卡死,这时catch就显得不可缺少了:

// 这里是没有catch,代码会报错,控制台会出现红色的提示 后面又操作也不会继续执行
getNumFn().then(
  res => {
    console.log(num); // 未定义的num,会报错
    console.log(res);
  },
  err => {
    console.log(num); // 未定义的num,会报错
    console.log(err);
  }
)

// 写上catch之后
getNumFn().then(
  res => {
    console.log(num);
    console.log(res);
  },
  err => {
    console.log(num);
    console.log(err);
  }
).catch(err => {
  console.log("有错误", err);
  // 后面有操作可继续执行
  console.log(123); // 会执行
})
// 控制台的错误提示不会显示为红色

catch方法会把错误原因传到err这个参数中,即便有代码也不会报错,与try/catch语句有相同的功能。

finally

finally方法,这个方法不接受任何参数,但是可以在回调函数中访问之前Promise的解决值或拒绝原因,无论 Promise 的状态如何,最终操作的消息都将打印到控制台。

// 简单拿上面获取随机数函数调一下 (很少用到)
getNumFn().finally(() => {
  console.log("我都会执行");
})

all的用法

all方法,是在所有异步操作都执行完之后才执行回调:

function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第一次请求");
      resolve("操作1");
    }, 1000)
  })
}

function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第二次请求");
      resolve("操作2");
    })
  }, 500)
}

function p3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("第三次请求");
      resolve("操作3");
    }, 100)
  })
}

Promise.all([ p1(), p2(), p3() ]).then(res => {
  console.log(res);
})

// 执行结果为:
// 第二次请求
// 第三次请求
// 第一次请求
// ["操作1", "操作2", "操作3"]

all方法接收一个数组参数,里面的值最终都算返回Promise对象。这样就算异步操作是并行执行的,all也会等他们全部执行完,才会进入then里面,三个异步操作的数据都被all放到一个数组中了。

race的用法

all方法和race方法区别在于:all全部执行完再执行回调,race一个执行完就执行回调

// 函数还是用上面的 p1、p2、p3
Promise.race([ p1(), p2(), p3() ]).then(res => {
  console.log(res);
})
// 执行结果:
// 第二次请求
// 操作2 // 这是 Promise.race的执行结果
// 第三次请求
// 第一次请求

总结

Promise不止这些,还有async await语法糖。

看一遍,实际用一遍,你就会了;学吧,学到了都是你自己的。


http://lihuaxi.xjx100.cn/news/1284958.html

相关文章

Kotlin语言介绍并推荐入门书籍(15本)

Kotlin是一种基于JVM&#xff08;Java Virtual Machine&#xff09;的静态类型编程语言&#xff0c;由JetBrains开发并于2017年正式发布。它旨在提供一种更简洁、更安全、更实用的替代方案来编写Java应用程序。以下是Kotlin语言的一些主要特点&#xff1a; 1. 与Java互操作性&…

Soybean Admin - 基于 Vue3 / vite3 等最新前端技术栈构建的中后台模板,免费开源、清新优雅,主题丰富

一款专业好看、完成度很高的 admin 开源项目&#xff0c;无论是用于生产还是学习&#xff0c;都非常值得尝试。 关于 Soybean Admin Soybean Admin 是一个基于 Vue3 / Vite3 / TypeScript / NaiveUI / Pinia 和 UnoCSS 的中后台模版&#xff0c;它使用了最新流行的前端技术栈…

TypeScript——类型别名(type)

类型别名 type 关键字&#xff0c;用来给一个类型起个新名字 定义类型别名 type str stringlet s: str 我是张三console.log(s)定义联合类型别名 type item string | numberlet arr: item[] [1, zs]console.log(arr)interface与类型别名 两者可以被继承 interface 继…

模板匹配

模板匹配 模板匹配和卷积原理很像&#xff0c;模板在原图像上从原点开始滑动&#xff0c;计算模板与图像窗口的差异程度&#xff0c;然后将每次计算的结果放入一个矩阵&#xff0c;最后作为结果输出。假设原图像大小为A * B&#xff0c;模板大小为a * b&#xff0c;则输出结果…

Docker 配置 vins-mono

前提条件&#xff1a;物理机上安装好ROS和Docker&#xff0c;参考 http://wiki.ros.org/ROS/Installation Ubuntu Docker 安装 | 菜鸟教程 Docker 换源 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["镜像…

Rancher集群搭建

前言 随着容器的普及和Kubernetes 的日渐成熟&#xff0c;企业内部运行多个Kubernetes 集群已变得颇为常见&#xff0c;然而部署kubernetes集群的方式也多样化&#xff0c;二进制部署、rancher、kubeadm、minikube等。然而本篇文章主要讲解的是如何使用rancher快速部署一个k8s集…

解释索引、事务、SQL优化等相关概念,并能够应用它们来解决实际问题

当涉及数据库和SQL的面试时&#xff0c;了解和应用以下概念可以提升你作为后台开发工程师的竞争力&#xff1a; 1. 索引&#xff1a; - 索引是一种数据结构&#xff0c;用于加速数据库的查询操作。 - 索引可以根据某个列或一组列的值进行排序&#xff0c;并提供快速访问…

一文学会Python安装和基础语法-全程干货

安装Python程序 官网Python下载地址&#xff1a;Python Releases for Windows | Python.org 安装3.7版本即可&#xff0c;版本升级可能会有较小的改动&#xff0c;下面要学习的都以python3.7为例 下载安装包之后直接双击无脑安装即可 新手经常遇到的问题&#xff1a; 1.符号…