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的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解JavaScript异步编程中jQuery的promise对象的作用

    Promise, 中文可以理解为愿望,代表单个操作完成的最终结果.一个Promise拥有三种状态:分别是unfulfilled(未满足的).fulfilled(满足的).failed(失败的),fulfilled状态和failed状态都可以被监听.一个愿望可以从未满足状态变为满足或者失败状态,一旦一个愿望处于满足或者失败状态,其状态将不可再变化.这种"不可改变"的特性对于一个Promise来说非常的重要,它可以避免Promise的状态监听器修改一个Promise的状态导致别的监听器的行

  • async/await与promise(nodejs中的异步操作问题)

    举例写文章详情页面的时候的一个场景:首先更改文章详情中的 PV,然后读取文章详情,然后根据文章详情中文章 Id 查阅该文章评论和该文章作者信息.获取全部数据之后渲染文章详情页.数据库操作都是异步的,最直接想到的办法就是一层一层的回调函数,问题出来了:十分不雅观,要是层再多一点还会有更多麻烦.怎么解决?业内为了处理异步操作问题也是拼了,什么async,q,bluebird,co,处理方式不同,各有千秋,感兴趣可以了解一下,但是惊喜的发现nodejs 7.6已经默认支持ES7中的 async/awa

  • JavaScript异步编程之Promise的初步使用详解

    1. 概述 Promise对象是ES6提出的的异步编程的规范.说到异步编程,就不得不说说同步和异步这两个概念. 从字面意思理解同步编程的话,似乎指的是两个任务同步运行,如果这样理解就错了(至少笔者再没有接触到这个概念的时候有这种误解).同步和异步指的是代码指定执行的顺序(结构化编程范式的执行顺序总是由上至下,由前往后的),如果执行的顺序与代码的相同,就是同步:如果不同,就是异步. 最初,操作系统都是基于命令行的,所有的的语言设计出来也天然是同步的语句,在这种情况下,也不需要异步编程.但是很快,图

  • 详解JavaScript 异步编程

    异步的概念 异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念. 在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行).而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系. 简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效果更高: 以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:异步就是从主线程发射一

  • javascript使用Promise对象实现异步编程

    Promise对象是CommonJS工作组为异步编程提供的统一接口,是ECMAScript6中提供了对Promise的原生支持,Promise就是在未来发生的事情,使用Promise可以避免回调函数的层层嵌套,还提供了规范更加容易的对异步操作进行控制.提供了reject,resolve,then和catch等方法. 使用PROMISE Promise是ES6之后原生的对象,我们只需要实例化Promise对象就可以直接使用. 实例化Promise: var promise = new Promis

  • JS ES6异步解决方案

    最初使用回调函数 ​ 由于最初j s官方没有明确的规范,各种第三方库中封装的异步函数中传的回调函数中的参数没有明确的规范, 没有明确各个参数的意义, 不便于使用. ​ 但是node中有明确的规范 ​ node中的的回调模式: 1. 所有回调函数必须有两个参数,第一个参数表示错误,第二个参数表示结果 2. 所有回调函数必须作为函数最后的参数 3. 所有回调函数不能作为属性出现 es6 异步处理模型 Es6 出现以后, 官方就提出了异步处理的规范, 提出了一种适用于所有异步场景的处理模型.该模型有:

  • 如何在现代JavaScript中编写异步任务

    前言 在本文中,我们将探讨过去异步执行的 JavaScript 的演变,以及它是怎样改变我们编写代码的方式的.我们将从最早的 Web 开发开始,一直到现代异步模式. 作为编程语言, JavaScript 有两个主要特征,这两个特征对于理解我们的代码如何工作非常重要.首先是它的同步特性,这意味着代码将逐行运行,其次是单线程,任何时候都仅执行一个命令. 随着语言的发展,允许异步执行的新工件出现在场景中.开发人员在解决更复杂的算法和数据流时尝试了不同的方法,从而导致新的接口和模式出现. 同步执行和观察

  • JS异步的执行原理和回调详解

    一.JS异步的执行原理   我们知道JavaScript是单线程的,而浏览器是多线程的.单线程执行任务需要一个个排队进行,假如一个任务需要很长时间执行(像ajax需要较长时间),会直接导致无响应,后面的任务一直在等待执行.这时候就需要用到异步.   想了解异步,首先我们要知道浏览器有最基本的三个常驻线程: JS引擎线程,事件触发线程,GUI渲染线程.   其中JS引擎线程和事件触发线程共同构成了一种事件循环机制,而GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新保

  • 异步JavaScript编程中的Promise使用方法

    异步? 我在很多地方都看到过异步(Asynchronous)这个词,但在我还不是很理解这个概念的时候,却发现自己常常会被当做"已经很清楚"(* ̄? ̄). 如果你也有类似的情况,没关系,搜索一下这个词,就可以得到大致的说明.在这里,我会对JavaScript的异步做一点额外解释. 看一下这段代码: var start = new Date(); setTimeout(function(){ var end = new Date(); console.log("Time elap

  • 微信小程序 es6-promise.js封装请求与处理异步进程

    微信小程序 es6-promise.js封装请求与处理异步进程 下载es6-promise.js置于根目录下的libs文件夹下: 在根目录utils文件夹下新建httpsPromisify.js,即定义封装请求的方法 var Promise = require('../libs/es6-promise.min') function httpsPromisify(fn) { return function (obj = {}) { return new Promise((resolve, reje

随机推荐