上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

[radash 源码解析#2] `radash.isPromise` 的使用和源码实现

[复制链接]
  • TA的每日心情
    难过
    2024-4-24 18:57
  • 签到天数: 13 天

    [LV.3]偶尔看看II

    24

    主题

    31

    回帖

    281

    积分

    荣誉开发者

    积分
    281

    荣誉开发者油中2周年生态建设者喜迎中秋

    发表于 2024-4-24 02:51:52 | 显示全部楼层 | 阅读模式

    本帖最后由 Yiero 于 2024-4-24 02:57 编辑

    [radash 源码解析#2] radash.isPromise 的使用和源码实现

    0. 前言

    内容先行预览:

    • radash.isPromise() 函数的说明使用方法
    • 通过TypeScript 类型操作符 - is进行函数返回值的类型保护
    • radash.isFunction() 的简单说明

    上一篇 radash.try 用到了 isPromise , 所以这篇就讲一讲 isPromise 吧.

    1. 说明

    函数说明

    radash.isPromise( value ): boolean;

    本函数用于判断一个是否为 Promise , 采用最小 Promise 判断, ECMAScript 将符合最小 Promise 判断的值视为一个 Promise 对象.

    关于最小 Promise 判断, 详细可以看我之前发布的一篇文章 [JavaScript理论学习] 什么是Promise (含如何判断一个值是Promise) . 这里面详细解释了如何规定和判断一个 Promise .

    简单来说, 我们可以通过以下的定义, 来判断一个是否为 Promise :
    当一个函数或者对象, 拥有 .then 方法/属性, 并且该 .then 方法/属性是一个函数, 那么就可以称该函数/对象为一个 Promise 对象.

    也就是说, 如果满足以下三个条件, 那么该值就可以称为 Promise :

    • <value> 的类型是函数 / 对象;
    • <value> 存在属性 .then;
    • value.then 是一个函数.

    2. 使用

    用法

    引入 isPromise

    import { isPromise } from 'radash';

    使用 isPromise

    const value = new Promise( () => {} );
    
    if ( isPromise( value ) ) {
        value.then();
    }

    示例

    import { isPromise } from 'radash';
    
    ( async () => {
        console.log( isPromise( 22 ) );
        // -> false
    
        console.log( isPromise( new Promise( () => {} ) ) );
        // -> true
    
        console.log( isPromise( { then: () => {} } ) );
        // -> true
    } )();

    3. 源码实现 - TypeScript

    Step - 1: 实现 Promise 的三个判定

    • <value> 的类型是函数 / 对象;
    • <value> 存在属性 .then;
    • value.then 是一个函数.
    const isPromise = ( value: any ): boolean => {
        return Boolean(
            value
            && [ 'object', 'function' ].includes( typeof value )
            && value.then
            && typeof value.then === 'function',
        );
    };

    方法都大差不差, radash 的实现少了一步判断 value 的类型, 以及 radash 每一步都进行返回. 效率没差的, && 也是同样的短路返回.

    Step - 2: 繁琐的类型判断

    通过上述的实现之后, 可以判断一个值是否为 Promise , 但是因为类型不明确, 所以如果传入的值 xPromise , 还需要通过一步类型断言才能够将 x 视为一个 Promise , 使用 Promise 的方法.

    /*
    * 这里通过 counter 进行的三元运算符给 x 赋值是为了模拟正常代码环境中复杂的变量使用场景
    * 如果直接给 x 赋值, 这种简单场景 ts 会导致直接就推断出来了 x 的类型
    * */
    let counter = 1;
    let x: string | Promise<string> = counter ?
        '20'
        : new Promise( ( resolve ) => resolve( '20' ) );
    
    // 如果 x 是 Promise
    if ( isPromise( x ) ) {
        // 进行类型断言 x: Promise<string>
        ( <Promise<string>> x ).then( ( res ) => {
            console.log( res );
        } );
    }
    
    // 进行类型断言 x: string
    console.log( <string> x );

    ( <Promise<string>> x ).then( ( res ) => {} ); 如果不进行类型断言, 直接使用 x.then( ( res ) => {} ); , 将会抛出以下错误:
    TS2339: Property then does not exist on type string | Promise<string>
    Property then does not exist on type string .

    Step - 3: 给返回值添加上类型保护

    基于 Step - 2 的问题, 可以给 isPromise 的返回值添加上类型保护:

    类型保护的意思是: 返回值仍然是一个布尔值, 不过返回值声明不是直接通过 boolean 声明, 而是通过一个具体的值(参数)推断出来的, 如果返回的是 true , 则说明该 值(参数) 就是对应的类型, 不用进行额外的类型断言.

    这里 value is Promise<any> 的意思是: 如果 value 的类型是 Promise<any>, 那么返回值是 true, 反之则返回 false.
    在表面上看和直接书写返回值类型为 boolean 似乎没有区别, 但是通过这样推断出来的布尔值返回值, 可以在 value 返回 true 的时候, 自动地将 value 的类型推断为 Promise<any> , 而不用手动进行类型断言.

    // 将 isPromise 的返回值从 `boolean` 变成 `value is Promise<any>`
    const isPromise = ( value: any ): value is Promise<any> => {
        return Boolean(
            value
            && [ 'object', 'function' ].includes( typeof value )
            && value.then
            && typeof value.then === 'function',
        );
    };

    现在使用 isPromise 函数就不用进行类型断言了, 如果 isPromise( x ) 返回的值是 true, 那么 x 的类型就会自动被推断为 Promise<any> 了.

    let counter = 1;
    let x: string | Promise<string> = counter ?
        '20'
        : new Promise( ( resolve ) => resolve( '20' ) );
    
    if ( isPromise( x ) ) {
        x.then( ( res ) => {
            console.log( res );
        } );
    }
    
    console.log( x );

    4. radash 源码

    TypeScript

    import { isFunction } from 'radash';
    
    /**
     * This is really a _best guess_ promise checking.
     * You should probably use Promise.resolve(value) to be 100%
     * sure you're handling it correctly.
     */
    export const isPromise = ( value: any ): value is Promise<any> => {
        if ( !value ) return false;
        if ( !value.then ) return false;
        if ( !isFunction( value.then ) ) return false;
        return true;
    };

    radash 使用了内部的 radash.isFunction() 方法来判断一个值是否为函数.

    JavaScript

    import { isFunction } from 'radash';
    
    const isPromise = ( value ) => {
        // 判断 value 是否存在
        if ( !value )
            return false;
    
        // 判断 value.then 是否存在
        if ( !value.then )
            return false;
    
        // 判断 value.then 是否为一个函数
        if ( !isFunction( value.then ) )
            return false;
    
        // 三个判断都成功, 返回 true
        return true;
    };

    碎碎念之 radash.isFunction()

    在这里顺便讲一下 radash.isFunction() , 也许后面不会单独开一篇文章来讲了.

    radash.isFunction() 的实现其实让我蛮困惑的, 下面是它的实现:

    const isFunction = (value: any): value is Function => {
      return !!(value && value.constructor && value.call && value.apply)
    }

    typeof value === 'function' 是一定不会出现错误返回的, Symbol.toStringTag 也无法篡改 typeof 的返回值, 唯一的解释就是 IE 兼容.

    但是既然都用 radash , 那么 IE 肯定是不兼容的, 所以完全没有必要这么判断.


    所以如果我的想法, 我会把 isFunction 函数这样写:

    const isFunction = (value: any): value is Function => {
      return typeof value === 'function';
    }

    附页

  • TA的每日心情
    慵懒
    14 小时前
  • 签到天数: 811 天

    [LV.10]以坛为家III

    31

    主题

    552

    回帖

    1555

    积分

    荣誉开发者

    积分
    1555

    荣誉开发者新人进步奖油中2周年生态建设者新人报道挑战者 lv2油中3周年喜迎中秋

    发表于 2024-4-24 19:21:55 | 显示全部楼层

    本帖最后由 steven026 于 2024-4-24 19:32 编辑

    看错了 编辑了

    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表