zoukankan      html  css  js  c++  java
  • 神奇的 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 与 Nodejs Event 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')
    })

    广州品牌设计公司https://www.houdianzi.com PPT模板下载大全https://redbox.wode007.com

    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
  • 相关阅读:
    平衡二叉树
    二叉搜索树的最近公共祖先
    U-Boot> help, 命令集
    sprintf_s函数用法
    用keil编写的 C51错误 *** WARNING L1: UNRESOLVED EXTERNAL SYMBOL SYMBOL: ?C_START
    GPS时间系统概述和世界时系统
    浅析gcc、arm-linux-gcc和arm-elf-gcc关系
    如何删除电脑中使用过的COM端口
    飞鸽传书 绑定指定网卡
    UE 高亮 一个或多个关键字的方法
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14096982.html
Copyright © 2011-2022 走看看