当前位置:首页 > 科技  > 软件

八个Promise高级技巧,让你在前端开发中如虎添翼!

来源: 责编: 时间:2024-07-02 17:38:01 289观看
导读在JavaScript项目中,Promise 的使用是必不可少的。然而,我发现许多中高级前端开发人员仍然停留在常见的promiseInst.then()、promiseInst.catch()、Promise.all甚至async/await等常规实践上,并没有深入理解它们。实际上,P

在JavaScript项目中,Promise 的使用是必不可少的。然而,我发现许多中高级前端开发人员仍然停留在常见的promiseInst.then()、promiseInst.catch()、Promise.all甚至async/await等常规实践上,并没有深入理解它们。Sub28资讯网——每日最新资讯28at.com

实际上,Promise 有许多巧妙的高级用法,其中一些在ALOVA请求策略库中广泛使用。Sub28资讯网——每日最新资讯28at.com

ALOVA 是一个基于 Promise 的请求策略库,旨在帮助开发者更高效地进行 HTTP 请求处理。它通过高级的 Promise 技巧,实现了请求共享、缓存、批量请求等功能,从而简化了前端开发中的数据请求管理。ALOVA 的目标是提供一种简洁、高效的方式来处理复杂的请求场景,减少代码冗余,提高应用性能。Sub28资讯网——每日最新资讯28at.com

现在,我将毫无保留地分享这些高级技巧。读完这篇文章后,将不再为相关问题感到困惑。Sub28资讯网——每日最新资讯28at.com

串行执行Promise数组

例如,如果你有一组需要串行执行的接口,首先你可能会想到使用 await。Sub28资讯网——每日最新资讯28at.com

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];for (const requestItem of requestAry) {  await requestItem();}

如果使用Promise语法,你可以使用then函数将多个Promise串起来,从而实现顺序执行。Sub28资讯网——每日最新资讯28at.com

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];const finallyPromise = requestAry.reduce(    (currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),    Promise.resolve() // 创建一个初始Promise以串联数组中的Promise。);

2.在新Promise作用域外改变状态

假设你有一些页面上的函数需要在使用前收集用户信息,那么你会如何实现呢?一种方法是在点击某个功能前弹出信息收集对话框。Sub28资讯网——每日最新资讯28at.com

不同层次的前端开发人员有不同的实现思路:Sub28资讯网——每日最新资讯28at.com

  • 初级前端:我会创建一个模态框,然后复制粘贴到其他页面以提高效率!
  • 中级前端:你的方法不利于维护。我们应该将这个组件单独封装,并在需要的页面中导入使用!
  • 高级前端:封装该封装的!写在一个所有页面都能调用的地方,不是更好吗?

让我们看看高级前端是如何实现的。以Vue3为例,看下面的示例。Sub28资讯网——每日最新资讯28at.com

<!-- App.vue --><template>  <!-- 以下是模态组件。 -->  <div class="modal" v-show="visible">    <div>      用户名:<input v-model="info.name" />    </div>    <!-- 其他信息 -->    <button @click="handleCancel">取消</button>    <button @click="handleConfirm">提交</button>  </div>  <!-- 页面组件 --></template><script setup>import { provide } from 'vue';const visible = ref(false);const info = reactive({  name: ''});let resolveFn, rejectFn;// 将信息收集函数传递给以下内容。provide('getInfoByModal', () => {  visible.value = true;  return new Promise((resolve, reject) => {    // 将两个函数赋值给外部,突破Promise作用域。    resolveFn = resolve;    rejectFn = reject;  });});const handleConfirm = () => {  resolveFn && resolveFn(info);};const handleCancel = () => {  rejectFn && rejectFn(new Error('用户已取消。'));};</script>

接下来,直接调用 getInfoByModal 即可使用模态框,并轻松获取用户填写的数据。Sub28资讯网——每日最新资讯28at.com

<template>  <button @click="handleClick">填写信息</button></template><script setup>import { inject } from 'vue';const getInfoByModal = inject('getInfoByModal');const handleClick = async () => {  // 调用后,会显示模态框。当用户点击“确认”时,Promise会变为fulfilled状态,从而获取用户信息。  const info = await getInfoByModal();  await api.submitInfo(info);}</script>

这也是许多UI组件库中封装常用组件的方法。Sub28资讯网——每日最新资讯28at.com

3.async/await的替代用法

许多人只知道在调用 async function 时使用 await 接收返回值,但他们不知道async函数实际上是返回Promise的函数。例如,以下两个函数是等价的:Sub28资讯网——每日最新资讯28at.com

const fn1 = async () => 1;const fn2 = () => Promise.resolve(1);fn1(); // 同样返回一个值为1的Promise对象。

在大多数情况下,await 后面跟着的是一个Promise对象,并等待其变为 fulfilled 状态。因此,下面的函数 fn1 等待也是等价的:Sub28资讯网——每日最新资讯28at.com

await fn1();const promiseInst = fn1();await promiseInst;

然而,await 有一个不为人知的秘密。当它后面跟的是非Promise对象的值时,它会使用 Promise 对象包装该值。因此,await 后的代码总是异步执行的。例如:Sub28资讯网——每日最新资讯28at.com

Promise.resolve().then(() => {  console.log(1);});await 2;console.log(2);// 输出顺序:1 2

等价于Sub28资讯网——每日最新资讯28at.com

Promise.resolve().then(() => {  console.log(1);});Promise.resolve().then(() => {  console.log(2);});

4.使用Promise实现请求共享

当一个请求已经发送但尚未响应时,再次发出相同请求会导致浪费请求。此时,我们可以与第二个请求共享第一个请求的响应。Sub28资讯网——每日最新资讯28at.com

request('GET', '/test-api').then(response1 => {  // ...});request('GET', '/test-api').then(response2 => {  // ...});

以上两个请求只会发送一次,并同时接收相同的响应值。Sub28资讯网——每日最新资讯28at.com

那么,请求共享有哪些场景呢?我认为有三种:Sub28资讯网——每日最新资讯28at.com

  • 当一个页面同时渲染多个内部组件获取数据时;
  • 提交按钮未禁用,用户连续多次点击提交按钮;
  • 在预加载数据的情况下,进入预加载页面前完成预加载;

这也是Alova的高级功能之一。实现请求共享需要使用 Promise 缓存功能。也就是说,一个Promise对象可以通过多次await调用获取数据。简单的实现思路如下:Sub28资讯网——每日最新资讯28at.com

const pendingPromises = {};function request(type, url, data) {  // 使用请求信息作为唯一请求键来缓存正在请求的Promise对象。  // 具有相同键的请求将重用该Promise。  const requestKey = JSON.stringify([type, url, data]);  if (pendingPromises[requestKey]) {    return pendingPromises[requestKey];  }  const fetchPromise = fetch(url, {    method: type,    data: JSON.stringify(data)  })  .then(response => response.json())  .finally(() => {    delete pendingPromises[requestKey];  });  return pendingPromises[requestKey] = fetchPromise;}

5.如果同时调用resolve和reject会发生什么?

大家都知道,Promise有三种状态:pending、fulfilled 和 rejected。但在下面的例子中,Promise 的最终状态是什么?Sub28资讯网——每日最新资讯28at.com

const promise = new Promise((resolve, reject) => {  resolve();  reject();});

正确答案是 fulfilled 状态。我们只需记住,一旦Promise从pending状态转变为其他状态,就不能再改变。因此,在这个例子中,调用 resolve()后,即使调用 reject(),状态也不会再改变。Sub28资讯网——每日最新资讯28at.com

6.彻底弄清then/catch/finally的返回值

总结一句话,上述三个函数都会返回一个新的 Promise包装对象,包装的值是执行回调函数的返回值。如果回调函数抛出错误,它将包装一个处于 rejected 状态的 Promise。这不太容易理解,我们来看一个例子:Sub28资讯网——每日最新资讯28at.com

你可以将它们一个一个复制并在浏览器控制台中运行,以更好地理解。Sub28资讯网——每日最新资讯28at.com

// then函数Promise.resolve().then(() => 1); // return new Promise(resolve => resolve(1))Promise.resolve().then(() => Promise.resolve(2)); // return new Promise(resolve => resolve(Promise.resolve(2)))Promise.resolve().then(() => {  throw new Error('abc')}); // return new Promise(resolve => resolve(Promise.reject(new Error('abc'))))Promise.reject().then(() => 1, () => 2); // return new Promise(resolve => resolve(2))// catch函数Promise.reject().catch(() => 3); // return new Promise(resolve => resolve(3))Promise.resolve().catch(() => 4); // 返回值是一个新的Promise,解析为调用catch的Promise对象。// 当finally函数的返回值不是Promise时,返回finally函数之前的Promise对象。Promise.resolve().finally(() => {}); // return Promise.resolve()Promise.reject().finally(() => {}); // return Promise.reject()// 当finally函数的返回值是Promise时,等待返回的Promise解析后再返回finally函数之前的Promise对象。Promise.resolve(5).finally(() => new Promise(res => {  setTimeout(res, 1000);})); // 返回一个处于pending状态的Promise,1秒后解析为5。Promise.reject(6).finally(() => new Promise(res => {  setTimeout(res, 1000);})); // 返回一个处于pending状态的Promise,1秒后抛出数字6。

7.then函数的第二个回调与catch回调有何不同?

Promise中的 then 函数的第二个回调和 catch 函数在请求失败时都会被触发。乍一看,它们似乎没有太大区别,但实际上,前者无法捕捉当前 then 函数第一个回调函数中抛出的错误,而 catch 函数可以。Sub28资讯网——每日最新资讯28at.com

Promise.resolve().then(  () => {    throw new Error('成功回调中的错误');  },  () => {    // 不会执行  }).catch(reason => {  console.log(reason.message); // 打印“成功回调中的错误”});

其原理如前所述,catch 函数是在 then 函数返回的 Promise 处于 rejected 状态时调用的,自然可以捕捉到其错误。Sub28资讯网——每日最新资讯28at.com

8.实现Koa2洋葱模型中的Promise

Koa2框架引入了洋葱模型,使你的请求像剥洋葱一样逐层处理,按相反顺序进入和退出层,从而实现请求的统一前后处理。Sub28资讯网——每日最新资讯28at.com

图片图片Sub28资讯网——每日最新资讯28at.com

我们看看一个简单的Koa2洋葱模型:Sub28资讯网——每日最新资讯28at.com

const app = new Koa();app.use(async (ctx, next) => {  console.log('a-start');  await next();  console.log('a-end');});app.use(async (ctx, next) => {  console.log('b-start');  await next();  console.log('b-end');});app.listen(3000);

上面的输出是 a-start -> b-start -> b-end -> a-end。这种神奇的输出顺序是如何实现的呢?我用了大约20行代码实现了这个简单的实现,巧合的是,它与Koa相似。Sub28资讯网——每日最新资讯28at.com

接下来,让我们进一步分析。Sub28资讯网——每日最新资讯28at.com

保存中间件函数,然后在 listen 函数中接收到请求时调用洋葱模型的执行。Sub28资讯网——每日最新资讯28at.com

function action(koaInstance, ctx) {  // ...}class Koa {  middlewares = [];  use(mid) {    this.middlewares.push(mid);  }  listen(port) {    // 模拟接收请求的伪代码    http.on('request', ctx => {      action(this, ctx);    });  }}

接收到请求后,从第一个中间件开始按顺序执行前置逻辑,调用 next。Sub28资讯网——每日最新资讯28at.com

// 开始中间件调用。function action(koaInstance, ctx) {  let nextMiddlewareIndex = 1; // 标识下一个执行的中间件索引    // 定义next函数。  function next() {    // 在剥洋葱之前,调用next会调用下一个中间件函数。    const nextMiddleware = middlewares[nextMiddlewareIndex];    if (nextMiddleware) {      nextMiddlewareIndex++;      nextMiddleware(ctx, next);    }  }  // 从第一个中间件函数开始执行,并传入ctx和next函数。  middlewares[0](ctx, next);}

处理“next”之后的后置逻辑Sub28资讯网——每日最新资讯28at.com

function action(koaInstance, ctx) {  let nextMiddlewareIndex = 1;  function next() {    const nextMiddleware = middlewares[nextMiddlewareIndex];    if (nextMiddleware) {      nextMiddlewareIndex++;      // 这里还添加了一个return,使中间件函数的执行通过Promise从后向前连接(建议反复理解这个return)。      return Promise.resolve(nextMiddleware(ctx, next));    } else {      // 在最后一个中间件的前置逻辑执行完后,返回的fulfilled Promise将开始执行next之后的后置逻辑。      return Promise.resolve();    }  }  middlewares[0](ctx, next);}

Sub28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-98199-0.html八个Promise高级技巧,让你在前端开发中如虎添翼!

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 一文搞懂Nginx配置:轻松驾驭多域名管理的实战攻略

下一篇: ASP.NET Core 极简 API 在 .NET 6 中的新特性探索

标签:
  • 热门焦点
  • 6月iOS设备性能榜:M2稳居榜首 A系列只能等一手3nm来救

    没有新品发布,自然iOS设备性能榜的上榜设备就没有什么更替,仅仅只有跑分变化而产生的排名变动,毕竟苹果新品的发布节奏就是这样的,一年下来也就几个移动端新品,不会像安卓厂商,一
  • 三言两语说透设计模式的艺术-简单工厂模式

    一、写在前面工厂模式是最常见的一种创建型设计模式,通常说的工厂模式指的是工厂方法模式,是使用频率最高的工厂模式。简单工厂模式又称为静态工厂方法模式,不属于GoF 23种设计
  • Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • CSS单标签实现转转logo

    转转品牌升级后更新了全新的Logo,今天我们用纯CSS来实现转转的新Logo,为了有一定的挑战性,这里我们只使用一个标签实现,将最大化的使用CSS能力完成Logo的绘制与动画效果。新logo
  • 微信语音大揭秘:为什么禁止转发?

    大家好,我是你们的小米。今天,我要和大家聊一个有趣的话题:为什么微信语音不可以转发?这是一个我们经常在日常使用中遇到的问题,也是一个让很多人好奇的问题。让我们一起来揭开这
  • 小米MIX Fold 3下月亮相:今年唯一无短板的全能折叠屏

    这段时间以来,包括三星、一加、荣耀等等有不少品牌旗下的最新折叠屏旗舰都有新的进展,其中荣耀、三星都已陆续发布了最新的折叠屏旗舰,尤其号荣耀Magi
  • 华为Mate 60系列用上可变灵动岛:正式版体验将会更出色

    这段时间以来,关于华为新旗舰的爆料日渐密集。据此前多方爆料,今年华为将开始恢复一年双旗舰战略,除上半年推出的P60系列外,往年下半年的Mate系列也将
  • iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    经过了一段时间的密集爆料,昨日iQOO官方如期对外宣布:将于5月23日推出全新的iQOO Neo8系列新品,官方称这是一款拥有旗舰级性能调校的作品。随着发布时
  • 回归OPPO两年,一加赢了销量,输了品牌

    成为OPPO旗下主打性能的先锋品牌后,一加屡创佳绩。今年618期间,一加手机全渠道销量同比增长362%,凭借一加 11、一加 Ace 2、一加 Ace 2V三款爆品,一加
Top