热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

JS异步代码单元测试之神奇的Promise

这篇文章主要介绍了JS异步代码单元测试之神奇的Promise,对异步感兴趣的同学,可以参考下

前言

写这篇文章的起因是在写单元测试时,做形如下测试时

new Promise((resolve, reject) => reject(1)).then().catch(err => {
    console.log(err)
})
async function jestTest () {
    await Promise.resolve().then()
    console.log('这个时候catch预期已经被调用,且输出日志')
}
jestTest()

无法使用await将测试代码恰好阻塞到catch在Event Loop中被调用后的时机,从而检测到catch的执行,通过测试。

而使用“神奇”一词则是因为 promsie 的链式调用中确实有很多默认的 handler 和值的隐含传递。

promise 的链式调用

为了不浪费大家的时间,我们先看一个例子:

Promise.resolve('promise1')
.then(res => {
    console.log('promise1-1 then')
})
.then(res => {
    console.log('promise1-2 then')
})
.then(res => {
    console.log('promise1-3 then')
})
.then(res => {
    console.log('promise1-4 then')
})


Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
    throw new Error('mock error 1')
})
.then(res => {
    console.log('promise2-2 then')
    throw new Error('mock error 2')
})
.catch(err => {
    console.log(err)
})

如果你答出的上述代码的输出顺序与下述相同,那么你可以跳过这篇文章:

promise1-1 then

promise2-1 then

promise1-2 then

promise1-3 then

Error: mock error 1

promise1-4 then

首先有一个前提,就是你已经知道了,这两个 promise 的 then 的调用是交叉入栈的(从头三行输出也能看出来),如果不清楚这部分内容,可以查阅 Event Loop 的相关文章,同时需要注意的是,在文章所指明的版本中 Chrome 与 NodejsEvent Loop 机制已经相同。

MDN 的错误

我们去翻阅下原本(我做了修改) MDN 关于 catch 的一段描述:

Basically, a promise chain stops if there's an exception, looking down the chain for catch handlers instead.

链式调用在发生异常时会停止,在链上查找 catch 语句来执行。

我最初的误解与此相同,误以为 catch 会直接抓到第一个throw Error,即Error会在promise1-2之后输出,即promise2-2所在的then并不会被加入调用栈。

而通过观察实际的输出结果发现并非如此,那么可以说明 MDN 解释的字面意思应该是错的,链式调用并没有停止,而是执行了我们没看到的东西。

链式的默认处理

这时我们需要知道then的一个默认处理,同样直接引用 MDN 的描述:

If the Promise that then is called on adopts a state (fulfillment or rejection) for which then has no handler, a new Promise is created with no additional handlers, simply adopting the final state of the original Promise on which then was called.

如果你的 promise 的 then 缺少了对应状态处理的回调,那么 then 会自动生成一个接受此 promise 状态的 promise,即 then 会返回一个状态引用相同的 promsie,交给后续的调用。

那么上述代码中的第二个 promise 部分就等效于

Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
    throw new Error('mock error 1')
})
.then(res => {
    console.log('promise2-2 then')
    throw new Error('mock error 2')
// 注意这个 onRejected
}, (err) => {
    return Promise.reject(err)
})
.catch(err => {
    console.log(err)
})

也就是说在输出结果的promise1-2和promise1-3之间是执行了promise2-2所在的then的,也就是说链式调用并没有直接停止,promise2-2所在的then还是被加入了调用栈。而catch并不是直接catch的第一个then抛出的错误,而是这个隐藏的onRejected返回的同样状态的promise。

简写

同理我们需要知道的是,catch(onRejected)是then(undefined, onRejected)的简写,即就算调用链的前置调用没有发生错误,catch也是会进入调用栈而非直接跳过的。

Promise.resolve('promise1')
.then(res => {
    console.log('promise1-1 then')
})
.then(res => {
    console.log('promise1-2 then')
})
.then(res => {
    console.log('promise1-3 then')
})


Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
})
.catch(err => {
    console.log(err)
})
.then(res => {
    console.log('其实我是 promise2-3 then')
})

async await

首先需要注意的是在文章指明的 NodeJs 和 Chrome 版本中,f(await promise)完全等同于promise.then(f)。

当然,讨论promise的时候,我们也不能抛开async await。虽然两者在 promise 状态为 onResolve 时处理逻辑相同,但错误处理的执行逻辑并不一样,在async await中发生错误时,才是真正的直接跳过后续await的执行

const promiseReject = new Promise((resolve, reject) => {
    reject(new Error('错误'))
})
const promiseResolve1 = new Promise((resolve, reject) => {
    resolve('正确')
})
const promiseResolve2 = new Promise((resolve, reject) => {
    resolve('正确')
})
const promiseResolve3 = new Promise((resolve, reject) => {
    resolve('正确')
})
function demo1 () {
    promiseReject
    .then(() => {
        console.log('1-1')
    })
    .catch(err => {
        console.log('1-2')
    })
}

async function demo2 () {
    try {
        await promiseReject
        await promiseResolve1
        await promiseResolve2
        await promiseResolve3
    } catch (error) {
        console.log('2-1')
    }
}
// 2-1
// 1-2

以上就是JS异步代码单元测试之神奇的Promise的详细内容,更多关于JS异步代码之Promise的资料请关注其它相关文章!


推荐阅读
  • 本文介绍了如何利用Selenium和Python通过执行JavaScript代码来控制网页中的滚动条,包括垂直和水平滚动条的控制,以及特定元素的聚焦技术。 ... [详细]
  • Web App vs Native App:未来的移动应用趋势
    随着移动互联网的发展,Web App和Native App之间的竞争日益激烈。对于开发者而言,选择哪一种技术路径更为明智?本文将深入探讨两种应用模式的特点及未来趋势。 ... [详细]
  • 本文介绍了如何使用Selenium中的Keys类来模拟各种键盘操作,包括普通按键和组合键(如Ctrl+A)的使用方法。 ... [详细]
  • Exploring the issue where the onScroll event does not correctly capture clientX and clientY values across different browsers. ... [详细]
  • 本文介绍了如何使用Selenium库中的Keys模块来模拟键盘操作,包括常见的组合键和单个按键的使用方法。 ... [详细]
  • 本文档将指导您如何下载并安装微信Web开发者工具,以及如何利用此工具进行微信网页授权的调试。此外,还将介绍如何使用该工具模拟JSSDK权限校验及移动设备调试等功能。 ... [详细]
  • 从 Windows 转向 Mac 的开发者指南:必备技巧与工具
    本文旨在帮助从 Windows 转向 Mac 的开发者们,提供一系列实用的技巧和工具,确保过渡过程顺畅。 ... [详细]
  • 精选6款开源Web性能优化工具,助力前端开发
    本文精选了6款开源Web性能优化工具,旨在帮助前端开发者提升页面加载速度,改善用户体验。 ... [详细]
  • 本文探讨了在使用HTML5 WebSocket技术构建浏览器内聊天室时遇到的连接不稳定问题,并提供了可能的解决方案和调试方法。 ... [详细]
  • 动画队列的设计目的是为了确保一系列任务能够按照预定顺序执行,每个任务只有在其前一个任务完成后才开始。这些任务既可以是同步的,也可以是异步的。本文将探讨jQuery动画系统中的队列机制,并介绍如何使用队列来优化动画效果。 ... [详细]
  • 本文详细介绍了如何在Apache Shiro框架中实现对并发登录人数的限制,包括配置和自定义过滤器的具体步骤。 ... [详细]
  • 本文探讨了如何在一个Python脚本中定义一个方法来生成特定URL,并在Robot Framework测试环境中调用此方法,通过环境变量启动测试案例。文中还提供了一个具体的实例,展示了正确的调用方式及可能遇到的问题解决方案。 ... [详细]
  • 本文基于作者使用Flask框架处理后端逻辑和原生JavaScript及jQuery进行前端开发的经验,详细介绍了如何在前后端之间高效地传输JSON数据。文章不仅涵盖了技术实现细节,还提供了实用的代码示例。 ... [详细]
  • 本文介绍了如何使用遗传算法来解决加工部件与加工机器之间的最佳匹配问题。研究结果显示,算法具有良好的收敛性能,但在某些情况下可能因样本量不足而导致过早收敛。研究旨在通过遗传算法寻找最优的加工部件分配方案,以最小化加工时间。 ... [详细]
  • 本文介绍了三款实用的Chrome插件:Gooreplacer用于加速访问依赖特定外部接口的网站;HTTP Request Blocker帮助过滤无法加载的图片请求;Browse Manager则能有效阻止恼人的弹出广告。通过合理配置这些工具,可以显著提升上网体验。 ... [详细]
author-avatar
孤l叶_991
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有