运用高阶函数优化Promise的状态访问和执行问题

前言

Promise,ES6带给前端开发者们最好的礼物,把广大人民群众从异步+回调地狱中解救了出来,在async/await语法糖还没诞生之前,仿佛神兵天降,如今,await泛滥的今天,却已黯然失色不少。笔者今日看到希沃ENOW大前端的一篇文章当面试官问Promise的时候他想知道什么 当中有提及Promise的3个不足之处。

image.png
或许就是这样那样的不足之处,让曾经作为神兵利器的它不如语法糖那般呼风唤雨吧。

优化不足

so?当面试官问你一个这样的问题:Promise有什么不足之处呢?或许可以滔滔不绝,娓娓道来,源远流长,甚至可以长篇大论。但是这时候如果被杀回马枪,反将一军,反问一句,如何解决这些不足之处呢?这时候或许就会望而生畏,不知所以了吧。

笔者静心思索一番,采用了高阶函数进行抽象,可以稍许改善以上的不足之处。

逻辑与Promise分离

首先,我们把Promise和内部的副作用(延时脚本/ajax请求等等)逻辑完全分离,只保留接口(res, rej)部分。

function timeoutLog(res, rej) {
    setTimeout(() => {
        res('look at me !!!!, i am here');
    }, 5000);
}
复制代码

timeoutLog 延迟5秒 返回一个结果,而整个函数/逻辑与promise完全解耦。这种写法非常类似一些node server比如express/egg的中间件,只保留了参数(res, rej)这些接口。

高阶函数

原理很简单,函数返回函数就可以了

const useStatusInPromise = f => <T extends any>():[Promise<T>, {value: 'pending' | 'done' | 'rejected'}] => {
    // 前面可以加一些对f的健康检查
    
    // 内部保存的promise状态
    const status = {
        value: 'pending'
    };
    const promise = new Promise(f).then(res => {
        status.value = 'done';
        return res;
    }).catch(err => {
        status.value = 'rejected';
        return err;
    }).finally(() => {
        Object.freeze(status); // 冻结status,防止被重新改写状态
    });

    return [promise as Promise<T>, status as any];
}
复制代码

结果

// 这里为止promise并不会立即执行 解决一旦新建就会立即执行的不足
// 如果想中途取消就不需要调用makeStatusPromiseLog
const makeStatusPromiseLog = useStatusInPromise(timeoutLog); 

// 执行makeStatusPromiseLog 并拿到promise实例和状态对象;
// 这时候才会执行promise
// 同时返回status 在执行环境中都可以访问到promise的最新状态
// 解决了第3点的不足
// 同时泛型约束返回的promise结果类型
const [promise, statusObj] = makeStatusPromiseLog<string>(); 

const interval = setInterval(() => {
    
    // 执行环境中可以访问promise的状态
    if (statusObj.value === 'done' || statusObj.value === 'rejected') {
        clearInterval(interval);

        promise.then(res => {
            console.log('timeoutLog end, the result is -> ', res) // 之后在处理promise 依然可以拿到结果
        }).catch(console.error);

        // 测试一下重复更改promise状态对象 会抛出异常 修改无效
        statusObj.value = 'pending'
    } else {
        console.log('继续检查promise的状态', statusObj.value);
    }
}, 1000);
复制代码

image.png

异常处理

对于第2个不足之处,笔者也没想到什么好的解决方法。对于这个场景只能在promise的内部逻辑中自行try catch

function timeoutLog(res, rej) {
    setTimeout(() => {

        // 为防止内部抛出的错误不会被反馈到外部
        //  没想到好办法 只能手动内部try catch
        // try {
        //     throw new Error('happen error')
        // } catch (err) {
        //     rej(err)
        // }
        res('look at me! I am here');
    }, 5000);
}
复制代码

总结

高阶函数是个非常有用的武器,特别是在函数式编程当中随处可见,把高阶运用到一般业务中也可以解决很多的问题。比如promise的一些不足之处也是可以改善的~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享