Zonejs经验汇总

2021-08-29 fishedee 前端

0 概述

Zone.js,是一个在js环境中实现类似ThreadLocal的工具。

但能力不止这样,在SpringBoot的环境中,一条请求处理堆栈里面,他们都是同一个线程的,所以我们能轻松使用ThreadLocal来获取这条请求处理链的信息。但是,在js的环境中,请求是可能经过async/await回调,setTimeout回调,ajx回调,来完成一整套请求的,由于js环境的复杂性,所有的请求都在同一个线程中执行,不同请求的处理链也在同一个线程中执行,所以,你用全局变量是得不到同一条请求处理链的信息的。

因此,在传统的js处理链中,你要在处理链中传递信息,你就只能在所有的处理方法中都加入一个Context参数,就像golang的Ctx一样,每个请求都协商好层层透传这个Context参数。但是,这样做明显很麻烦呀,对处理方法的侵入性太大了。

Zone.js要解决的就是这个问题,解决步骤为:

  • 创建一个Zone,写入属性信息,properties
  • 用这个Zone的run或者runGuard方法来触发请求方法
  • 那么请求方法里面,即使嵌套了async/await回调,setTimeout回调,ajx回调,后的任意闭包位置,都能通过全局Zone对象来获取这个properties!

Zone.js可以说是在库的基础上,补充了js的语法不足

Demo的代码在这里

npm install zone.js --save

安装依赖

1 properties与run

import 'zone.js';

//rootZone
console.log(Zone.current);

//在rootZone作为parent的情况下,创建normalZone
let normalZone = Zone.current.fork({
    name:'normalZone',
    properties:{
        fish:'123'
    }
});

const go = ()=>{
    //默认Zone就是root,值为undefined
    console.log('go outer',Zone.current.get('fish'));

    //通过normalZone.run来切换zone
    normalZone.run(()=>{
        //当前闭包就是在normalZone中,值为123
        console.log('go inner',Zone.current.get('fish'));

    });
}

go();

const go2 = ()=>{
    //默认Zone就是root,值为undefined
    console.log('go2 outer',Zone.current.get('fish'));

    //通过normalZone.run来切换zone
    normalZone.run(async ()=>{
        //当前闭包就是在normalZone中
        setTimeout(()=>{
            //即使跨过了setTimeout,在另外一个调用栈中
            //当前依然还是在normalZone,这就是Zone.js的关键
            //不论跨过多少层setTimeout,async/await,当前Zone竟然能自动传递过去
            console.log('go2 inner',Zone.current.get('fish'));
        },10);
    });
}

go2();



const delay = (timeout:number)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(resolve,timeout);
    });
}

async function go3_2(){
    await delay(10);
    console.log('go3 inner',Zone.current.get('fish'));
}

async function go3_1(){
    await delay(10);
    await go3_2();
}


const go3 = ()=>{
    //默认Zone就是root,值为undefined
    console.log('go3 outer',Zone.current.get('fish'));

    //通过normalZone.run来切换zone
    normalZone.run(async ()=>{
        //有加await,能传递Zone
        await go3_1();

        //没有加await,也能传递Zone,实在屌
        go3_1();
    });
}

go3();

export default ()=>{
    return <div>{'123'}</div>;
}

如代码所示了,没啥好说的

2 Zone链

import 'zone.js';

//rootZone
console.log(Zone.current);

//在rootZone作为parent的情况下,创建normalZone
let normalZone = Zone.current.fork({
    name:'normalZone',
    properties:{
        fish:'123'
    }
});

//在normalZone作为parent的情况下,创建normalZone2
let normalZone2 = normalZone.fork({
    name:'normalZone',
    properties:{
        cat:'456'
    }
});



const delay = (timeout:number)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(resolve,timeout);
    });
}

async function go4_2(){
    await delay(10);
    //两个属性都能拿到,这是Zone链的意义
    console.log('go4 fish ',Zone.current.get('fish'));
    console.log('go4 cat ',Zone.current.get('cat'));
}

async function go4_1(){
    await delay(10);
    await go4_2();
}


const go4 = ()=>{
    //默认Zone就是root,值为undefined
    console.log('go4 outer',Zone.current.get('fish'));

    //通过normalZone.run来切换zone
    normalZone2.run(async ()=>{
        //有加await,能传递Zone
        await go4_1();
    });
}

go4();

export default ()=>{
    return <div>{'123'}</div>;
}

创建Zone的时候,是需要指定parent的。所以,Zone是知道每一个Zone的整个父子链是如何构成的。当Zone去拉取属性的时候,Zone是会先到当前Zone里面拉,拉不到就往父Zone去拉,不断循环到顶部Zone的。

3 onHandleError与runGuarded

import 'zone.js';


console.log(Zone.current);

let errorZone = Zone.current.fork({
  name:'errorZone',

  onHandleError:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any) => {
    console.log('catch error ',error);
    return false;
  }
});

//runGuarded才能捕捉错误
//可以捕捉直接的错误
errorZone.runGuarded(()=>{
  throw new Error("123");
});

const delay = (timeout:number)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(resolve,timeout);
    });
}

//可以捕捉异步错误
errorZone.runGuarded(async ()=>{
    await delay(10);
    throw new Error("456");
});

const myFun = async()=>{
    await delay(10);
    throw new Error("789");
}

//可以捕捉await异常里面的错误
errorZone.runGuarded(async()=>{
    console.log('inner');
    await myFun();
    console.log("outer");
    await myFun();
    console.log("outer2");
})

const myFun2 = async()=>{
    await delay(10);
    throw new Error("010");
}

//可以捕捉异步,但没有await的错误
errorZone.runGuarded(async()=>{
    console.log('inner2');
    myFun2();
    console.log("outer2");
})

export default ()=>{
    return (<div>{'123'}</div>);
}

Zone可以用runGuarded与onError组合来捕获异常的,注意这种方法依然有点小问题,具体看这里Demo

4 总结

目前为止,已知Zone.js与Antd Select组件冲突,一旦打开Zone.js,Antd Select就会卡死,所以前端慎用这个库,只推荐在后端使用。

参考资料:

相关文章