消除异步传染性,模拟实现React suspense

303 阅读1分钟

题目描述

先看代码............

题中原意为网络请求图片,在请求成功前展示suspense属性的内容。网络请求在js中为异步操作,注释中使用了async函数等待事件循环,但这会使得整个函数返回值也变为promise,如果多几层函数互相调用,就会出现很多的async与await。消除异步传染性要做的,就是要不使用目前js提供的异步解决方案(回调函数,promise,generator,async),仍使得代码逻辑正确。


<body>
    <div id="catPic" suspense="<h1>loading...</h1>"></div>
    <script>
        
        function getCatPic() {
            return window.fetch('https://placekitten.com/1920/1080')
        }

        // 把
        // async function testCom() {
        // 改为
        function testCom() {
            let imgContainer = document.querySelector('#catPic')
            imgContainer.innerHTML = imgContainer.getAttribute("suspense")

            // 把
            // let res = await getCatPic()
            // 改为
            let res = getCatPic()

            res.blob().then(blob => {
                let url = URL.createObjectURL(blob)
                imgContainer.innerHTML = `<img src="${url}" alt="cat" width="100%">`
            })
        }

        function render(fn) {
            // your code ...
        }

        // 把
        // testCom();
        // 改为
        render(testCom)
    </script>
</body>

解决思路

模拟async或generator的执行顺序,在执行到网络请求的时候先暂停,等网络请求回来了再拿回执行权。暂停用抛出错误的方式,但继续执行这里需要取巧,由于网络请求前的代码是渲染loading,所以这里即使执行两次也不会影响正确的逻辑。

那么我们要做的就是第一次执行的时候,到网络请求时抛出错误,网络请求成功后再次执行函数。确定好思路后编码就比较简单了。

编码


<body>
    <div id="catPic" suspense="<h1>loading...</h1>"></div>
    <script>
        
        function getCatPic() {
            return window.fetch('https://placekitten.com/1920/1080')
        }

        // 把
        // async function testCom() {
        // 改为
        function testCom() {
            let imgContainer = document.querySelector('#catPic')
            imgContainer.innerHTML = imgContainer.getAttribute("suspense")

            // 把
            // let res = await getCatPic()
            // 改为
            let res = getCatPic()

            res.blob().then(blob => {
                let url = URL.createObjectURL(blob)
                imgContainer.innerHTML = `<img src="${url}" alt="cat" width="100%">`
            })
        }

        // testCom();

        function render(fn) {
            let res = null;
            let originFetch = window.fetch
            window.fetch = function (...rest) {
                if (res) return res;
                let p = originFetch.apply(this, rest).then(e => res = e)
                throw p
            }
            try {
                fn()
            } catch (error) {
                error.then(() => {
                    fn()
                    window.fetch = originFetch
                })
            }
        }

        render(testCom)
    </script>
</body>