← 返回首页
如何获取setTimeout回调函数的返回值
发表时间:2022-11-25 23:05:25
如何获取setTimeout回调函数的返回值

setTimeout 是前端程序猿最经常使用的一个api,那么我们如何获得setTimeout 里回调函数的返回值呢?

1. setTimeout的异步特性

我们先看下面实例:

<script>
    function foo() {
        //延迟任务1
        setTimeout(() => {
            console.time('testTime')
            console.log('aaa....')
        }, 1000);
        //延迟任务2
        setTimeout(() => {
            console.log('bbb....')
        }, 2000);
        //延迟任务3
        setTimeout(() => {
            console.log('ccc....')
            console.timeEnd('testTime')
        }, 3000);
    }
    foo();
    console.log('ending...')
</script>

运行结果:

end...
aaa....
bbb....
ccc....
testTime: 1999.799072265625 ms

在foo函数里执行了三个延迟任务,我们发现总耗时取决于延时时间最长的任务3,而不是三个延迟任务时间的总和。

给人一种假象似乎setTimeout()一定是异步,但是事实是:setTimeout是单线程,类似异步,但不是真正的异步。

上面的代码改写如下:

<script>
    function foo() {
        //延迟任务1,立刻执行
        setTimeout(() => {
            console.time('testTime')
            console.log('aaa....')
        }, 0);
        //延迟任务2
        setTimeout(() => {
            console.log('bbb....')
        }, 2000);
        //延迟任务3
        setTimeout(() => {
            console.log('ccc....')
            console.timeEnd('testTime')
        }, 3000);
    }

    foo();
    console.log('ending...')
</script>

运行结果:

ending...
aaa....
bbb....
ccc....
testTime: 3000.72705078125 ms

我们发现,任务的执行顺序没有改变,而且总耗时反而增加了。所以说 setTimeout并不是真正的异步, 而是JavaScript在执行的时候会将setTimeout放入任务队列中等待主线程的执行(不阻塞主线程)全部执行完成后再通过event loop去询问任务队列中是否有可执行的代码,再继续放入主线程中执行,故产生了异步的假象。

结论:setTimeout()有异步的特性,但不是真正的异步。

2.如何获得setTimeout里回调函数的返回值

明白了setTimeout()有异步特性,下来咱们看看如何获取setTimeout里回调函数的返回值。看下面实例:

<script>
    function test(){
        let result = setTimeout(()=>{
            console.log('execute callback...')
            console.timeEnd('testTime')
            return{
                name: '张三'
            }
        },3000);

        return result;
    }

    console.time('testTime')
    console.log(test());
</script>

运行结果:

1
execute callback...
testTime: 3003.06591796875 ms

我们发现返回结果并不是‘张三’对象。而是整数 1,原来这个返回值是setTimeout方法的返回值,而不是setTimeout里面执行的回调函数的返回值。setTimeout是有返回值的, 表示当前setTimeout在页面中的所有setTimeout中的序号,并且默认从1开始。

于是很多小伙伴会想到改写如下:

<script>
    function test(){
        let result;
        setTimeout(result = ()=>{
            console.log('execute callback...')
            console.timeEnd('testTime')
            return{
                name: '张三'
            }
        },3000);
        return result;
    }

    console.time('testTime')
    console.log(test());
</script>

运行结果:

()=>{
            console.log('execute callback...')
            console.timeEnd('testTime')
            return{
                name: '张三'
            }
        }
execute callback...
testTime: 3006.216064453125 ms

我们发现返回了函数文本,并且是立即返回的结果。并不是回调函数的返回值。

还有小伙伴尝试改写如下:

   function fn(){
        console.log('execute callback...')
        console.timeEnd('testTime')
        return{
            name: '张三'
        }
    }

    function test(callback){
        let result;
        setTimeout(result = callback(),3000);
        return result;
    }

    console.time('testTime')
    console.log(test(fn));

运行结果如下:

execute callback...
testTime: 0.10498046875 ms
{name: '张三'}
Uncaught SyntaxError: Unexpected identifier 'Object'

我们发现输出的{name: '张三'},是result = callback() 这行代码里,函数调用的返回值,并且整个表达式是一个对象类型,不符合setTimeout第一参数规定的类型(1."字符串代码" 2.方法 3.函数)。

由于setTimeout的异步特性,以上传统思维方式都无法正确获得setTimout里执行回调函数的返回值。

3.实现方案

通过以上实验,我得到以下结论:不能return来自异步回调的值。咱们需要提供回调或返回一个promise来间接地解决这个问题。

1)使用回调函数

<script>
    function fn(resp) {
        console.log(resp);
        console.timeEnd('testTime')
    }

    function test(callback) {
        setTimeout(() => {
            console.log('execute callback...')
            callback({name: '张三'})
        }, 3000);
    }

    console.time('testTime')
    test(fn);
</script>

运行结果:

execute callback...
{name: '张三'}
testTime: 3008.704833984375 ms

2)使用Promise

<script>

    function fn() {
        let promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                console.log('execute....')
                resolve({name: '张三'});
            }, 3000);
        });
        return promise;
    }
    console.time('testTime')
    fn().then((resp)=>{
        console.log(resp);
        console.timeEnd('testTime')
    })
</script>

运行结果:

execute....
{name: '张三'}
testTime: 3028.7841796875 ms

推荐使用使用async/await,使得异步代码更像同步代码,可读性更好。

<script>
    async function fn() {
        let promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                console.log('execute....')
                resolve({name: '张三'});
            }, 3000);
        });
        return promise;
    }
    console.time('testTime');

    async function test () {
        const result = await fn();
        console.log(result);
        console.timeEnd('testTime')
    }
    test();
</script>