在JavaScript项目中,Promise 的使用是必不可少的。然而,我发现许多中高级前端开发人员仍然停留在常见的promiseInst.then()
、promiseInst.catch()
、Promise.all
甚至async/await
等常规实践上,并没有深入理解它们。
实际上,Promise 有许多巧妙的高级用法,其中一些在ALOVA请求策略库中广泛使用。
ALOVA 是一个基于 Promise 的请求策略库,旨在帮助开发者更高效地进行 HTTP 请求处理。它通过高级的 Promise 技巧,实现了请求共享、缓存、批量请求等功能,从而简化了前端开发中的数据请求管理。ALOVA 的目标是提供一种简洁、高效的方式来处理复杂的请求场景,减少代码冗余,提高应用性能。
现在,我将毫无保留地分享这些高级技巧。读完这篇文章后,将不再为相关问题感到困惑。
1.串行执行Promise数组
例如,如果你有一组需要串行执行的接口,首先你可能会想到使用 await
。
如果使用Promise语法,你可以使用then
函数将多个Promise串起来,从而实现顺序执行。
2.在新Promise作用域外改变状态
假设你有一些页面上的函数需要在使用前收集用户信息,那么你会如何实现呢?一种方法是在点击某个功能前弹出信息收集对话框。
不同层次的前端开发人员有不同的实现思路:
- 初级前端:我会创建一个模态框,然后复制粘贴到其他页面以提高效率!
- 中级前端:你的方法不利于维护。我们应该将这个组件单独封装,并在需要的页面中导入使用!
- 高级前端:封装该封装的!写在一个所有页面都能调用的地方,不是更好吗?
让我们看看高级前端是如何实现的。以Vue3为例,看下面的示例。
接下来,直接调用 getInfoByModal
即可使用模态框,并轻松获取用户填写的数据。
这也是许多UI组件库中封装常用组件的方法。
3.async/await的替代用法
许多人只知道在调用 async function
时使用 await
接收返回值,但他们不知道async
函数实际上是返回Promise的函数。例如,以下两个函数是等价的:
在大多数情况下,await
后面跟着的是一个Promise对象,并等待其变为 fulfilled
状态。因此,下面的函数 fn1
等待也是等价的:
然而,await
有一个不为人知的秘密。当它后面跟的是非Promise对象的值时,它会使用 Promise 对象包装该值。因此,await 后的代码总是异步执行的。例如:
等价于
4.使用Promise实现请求共享
当一个请求已经发送但尚未响应时,再次发出相同请求会导致浪费请求。此时,我们可以与第二个请求共享第一个请求的响应。
以上两个请求只会发送一次,并同时接收相同的响应值。
那么,请求共享有哪些场景呢?我认为有三种:
- 当一个页面同时渲染多个内部组件获取数据时;
- 提交按钮未禁用,用户连续多次点击提交按钮;
- 在预加载数据的情况下,进入预加载页面前完成预加载;
这也是Alova的高级功能之一。实现请求共享需要使用 Promise 缓存功能。也就是说,一个Promise对象可以通过多次await
调用获取数据。简单的实现思路如下:
5.如果同时调用resolve和reject会发生什么?
大家都知道,Promise有三种状态:pending
、fulfilled
和 rejected
。但在下面的例子中,Promise 的最终状态是什么?
正确答案是 fulfilled
状态。我们只需记住,一旦Promise从pending状态转变为其他状态,就不能再改变。因此,在这个例子中,调用 resolve()
后,即使调用 reject()
,状态也不会再改变。
6.彻底弄清then/catch/finally的返回值
总结一句话,上述三个函数都会返回一个新的 Promise包装对象,包装的值是执行回调函数的返回值。如果回调函数抛出错误,它将包装一个处于 rejected
状态的 Promise。这不太容易理解,我们来看一个例子:
你可以将它们一个一个复制并在浏览器控制台中运行,以更好地理解。
7.then函数的第二个回调与catch回调有何不同?
Promise中的 then
函数的第二个回调和 catch
函数在请求失败时都会被触发。乍一看,它们似乎没有太大区别,但实际上,前者无法捕捉当前 then
函数第一个回调函数中抛出的错误,而 catch
函数可以。
其原理如前所述,catch
函数是在 then
函数返回的 Promise 处于 rejected
状态时调用的,自然可以捕捉到其错误。
8.实现Koa2洋葱模型中的Promise
Koa2框架引入了洋葱模型,使你的请求像剥洋葱一样逐层处理,按相反顺序进入和退出层,从而实现请求的统一前后处理。
我们看看一个简单的Koa2洋葱模型:
上面的输出是 a-start -> b-start -> b-end -> a-end
。这种神奇的输出顺序是如何实现的呢?我用了大约20行代码实现了这个简单的实现,巧合的是,它与Koa相似。
接下来,让我们进一步分析。
保存中间件函数,然后在 listen
函数中接收到请求时调用洋葱模型的执行。
接收到请求后,从第一个中间件开始按顺序执行前置逻辑,调用 next
。
处理“next”之后的后置逻辑