Node.js 中 TimeoutPromise 封装

NodejsLearning

使用 Node.js 的同学, 一定免不了使用 Promise, 说到 Promise, 忍不住想要吹一锅 Node.js 了. Promise 配合 asyncawait, 同步异步随时切换, 完全告别了异步时回调嵌套产生的 ugly code.

我们假设这样一个需求, 一段业务逻辑,连续几个异步操作,非常常见的需求.

  1. 先看下最丑陋的搞法, 一堆回掉嵌套, 找打那种的

     (async function () {
         let data = 'happy_coding';
         setTimeout(() => {
             try {
                 data = `step[1]_${data}`;
                 console.log(data);
             } catch (error) {
                 console.log('error = ', error);
             }
             setTimeout(() => {
                 try {
                     data = `step[2]_${data}`;
                     console.log(data);
                 } catch (error) {
                     console.log('error = ', error);
                 }
                 setTimeout(() => {
                     try {
                         data = `step[3]_${data}`;
                         console.log(data);
                     } catch (error) {
                         console.log('error = ', error);
                     }
                 }, 1000);
             }, 1000);
         }, 1000);
     })();
    
  2. 再看下稍好点的情况

     function execAsync(sth) {
         return new Promise((resolve, reject) => {
             setTimeout(() => resolve(sth), 1000);
         })
     }
     
     (async function () {
         execAsync('happy_coding')
             .then((data) => {
                 let tmp = `step[1]_${data}`;
                 console.log(tmp);
                 return execAsync(tmp);
             })
             .then((data) => {
                 let tmp = `step[2]_${data}`;
                 console.log(tmp);
                 return execAsync(tmp);
             })
             .then((data) => {
                 let tmp = `step[3]_${data}`;
                 console.log(tmp);
                 return execAsync(tmp);
             })
             .catch((error) => {
                 console.log('error = ', error);
             });
     })();
    

把异步操作都装进 Promise, 然后 then...then...catch 就好, 当然, 对于非 js 研发同学, 上面的代码已经很漂亮了. 小夫当年写 Java 的时候, 突然有天出来个 RxJava, 就是上面这样的, 当时写起来那酸爽劲, 现在想起来还有些感动, 然后, 带着这份感动直至遇见 Node.js.

  1. 同步方式写异步

     function execAsync(sth) {
         return new Promise((resolve, reject) => {
             setTimeout(() => resolve(sth), 1000);
         })
     }
    
     (async function () {
         try {
             let data = await execAsync('happy_coding');
             data = `step[1]_${data}`;
             console.log(data);
             data = await execAsync(data);
             data = `step[2]_${data}`;
             console.log(data);
             data = await execAsync(data);
             data = `step[3]_${data}`;
             console.log(data);
         } catch (error) {
             console.log('error = ', error);
         }
     })();
    

以上, 把异步操作都装进 Promise 后, 如果想要同步调用, 那么前面 await 一下就好了, 如果不想让这个异步操作阻塞主流程, 那么去掉 await 就好了. 美不美, 简直美. 这可是面向未来的语法. 哈哈. 我知道的好像 Dart 里面也这样搞, Python 里引用 asyncio 也可以, 但是不向 Node.js 里面这么直接.

好了, 前面只是替 Node.js 吹个牛逼, 下面说正题了.

Node.js 中虽然提供了 Promise, 但是有的业务里不够用. 比如最近我的一个需求中就需要一个带超时时间的 Promise, 怎么搞嘞, 查了些许资料, 然后自己封装了下:

    class TimeoutPromise extends Promise {
        constructor(callback, ms = 30 * 1000) {
            let timeout;
            let wrapperPromise = Promise.race([
                new Promise(callback),
                new Promise((resolve, reject) => {
                    timeout = setTimeout(() => {
                        reject(new Error('PTIMEOUT'));
                    }, ms);
                }),
            ]);
    
            super((resolve, reject) => {
                wrapperPromise.then((data) => {
                    clearTimeout(timeout);
                    resolve(data);
                }).catch((error) => {
                    clearTimeout(timeout);
                    reject(error); // if timeout, reject the `PTIMEOUT` error
                })
            });
        }
    }

我们做下测试:

    (async function () {
        let start = Date.now();
    
        function test() {
            return new TimeoutPromise((resolve, reject) => {
                setTimeout(() => { // 模拟异步耗时操作
                    resolve('finished');
                }, 5000); // 异步任务超时时间设置为 5s
            }, 3000) // Promise 超时时间设置为 3s
        }
    
        try {
            await test(); // 该函数会在 3s 后执行完毕
        } catch (error) {
            console.log(' --> Error = ', error);
        }
        
        console.log('spent: ', (Date.now() - start) / 1000);
    })();

下面是执行结果:

     --> Error =  Error: PTIMEOUT
        at Timeout.setTimeout (/home/modorone/WorkSpace/test/timeout_promise/test04.js:18:28)
        at ontimeout (timers.js:475:11)
        at tryOnTimeout (timers.js:310:5)
        at Timer.listOnTimeout (timers.js:270:5)
    spent:  3.011

需要说明的是, await test() 会在 3s 后得到返回值, 但是 test() 中的异步操作依然会在自己的 timeout, 即 5s 之后完成.

小夫参考了 here. 里面实际上提到了两种实现, 我觉得 Promise.race() 比较美.

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 欢迎阅读专门探索 JavaScript 及其构建组件的系列文章的第四章。 在识别和描述核心元素的过程中,我们还分享...
    OSC开源社区阅读 1,163评论 1 10
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,729评论 1 56
  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 839评论 0 2
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,749评论 0 5
  • 2018年6月30日,膏肓的我,从很不确切的方向,带着求医的心情,背上康复的希望慢慢的向互加徐行。 行在...
    赫章1916魏勇阅读 361评论 4 1