美文网首页让前端飞
前端笔试题(异步专项练习)

前端笔试题(异步专项练习)

作者: 前端辉羽 | 来源:发表于2020-10-09 19:40 被阅读0次

    第一题:阅读下面代码,我们只考虑浏览器环境下的输出结果,写出它们结果打印的先后顺序,并分析出原因。

    console.log("AAAA");
    setTimeout(() => console.log("BBBB"), 1000);
    const start = new Date();
    while (new Date() - start < 3000) {}
    console.log("CCCC");
    setTimeout(() => console.log("DDDD"), 0);
    new Promise((resolve, reject) => {
            console.log("EEEE");
            foo.bar(100);
        })
        .then(() => console.log("FFFF"))
        .then(() => console.log("GGGG"))
        .catch(() => console.log("HHHH"));
    console.log("IIII");
    

    答案解析:
    这道题考察重点是 js异步执行 宏任务 微任务。
    一开始代码执行,输出AAAA. 1
    第二行代码开启一个计时器t1(一个称呼),这是一个异步任务且是宏任务,需要等到1秒后提交。
    第四行是个while语句,需要等待3秒后才能执行下面的代码,这里有个问题,就是3秒后上一个计时器t1的提交时间已经过了,但是线程上的任务还没有执行结束,所以暂时不能打印结果,所以它排在宏任务的最前面了。
    第五行又输出CCCC
    第六行又开启一个计时器t2(称呼),它提交的时间是0秒(其实每个浏览器器有默认最小时间的,暂时忽略),但是之前的t1任务还没有执行,还在等待,所以t2就排在t1的后面。(t2排在t1后面的原因是while造成的)都还需要等待,因为线程上的任务还没执行完毕。
    第七行new Promise将执行promise函数,它参数是一个回调函数,这个回调函数内的代码是同步的,它的异步核心在于resolve和reject,同时这个异步任务在任务队列中属于微任务,是优先于宏任务执行的,(不管宏任务有多急,反正我是VIP)。所以先直接打印输出同步代码EEEE。第九行中的代码是个不存在的对象,这个错误要抛给reject这个状态,也就是catch去处理,但是它是异步的且是微任务,只有等到线程上的任务执行完毕,立马执行它,不管宏任务(计时器,ajax等)等待多久了。
    第十四行,这是线程上的最后一个任务,打印输出 IIII
    我们先找出线程上的同步代码,将结果依次排列出来:AAAA CCCC EEEE IIII
    然后我们再找出所有异步任务中的微任务 把结果打印出来 HHHH
    最后我们再找出异步中的所有宏任务,这里t1排在前面t2排在后面(这个原因是while造成的),输出结果顺序是 BBBB DDDD
    所以综上 结果是 AAAA CCCC EEEE IIII HHHH BBBB DDDD

    第二题:阅读下面代码,我们只考虑浏览器环境下的输出结果,写出它们结果打印的先后顺序,并分析出原因。

    async function async1() {
        console.log("AAAA");
        async2();
        console.log("BBBB");
    }
    async function async2() {
        console.log("CCCC");
    }
    console.log("DDDD");
    setTimeout(function () {
        console.log("FFFF");
    }, 0);
    async1();
    new Promise(function (resolve) {
        console.log("GGGG");
        resolve();
    }).then(function () {
        console.log("HHHH");
    });
    console.log("IIII");
    

    答案解析:
    这道题考察重点是 js异步执行 宏任务 微任务.
    这道题的坑就在于 async中如果没有await,那么它就是一个纯同步函数。
    这道题的起始代码在第9行,输出DDDD
    第10行计时器开启一个异步任务t1(一个称呼),这个任务且为宏任务。
    第13行函数async1执行,这个函数内没有await 所以它其实就是一个纯同步函数,打印输出AAAA,
    在async1中执行async2函数,因为async2的内部也没有await,所以它也是个纯同步函数,打印输出CCCC
    紧接着打印输出BBBB。
    第14行new Promise执行里面的代码也是同步的,所以打印输出GGGG,resolve()调用的时候开启一个异步任务t2(一个称呼),且这个任务t2是微任务,它的执行交给then()中的第一个回调函数执行,且优先级高于宏任务(t1)执行。
    第20行打印输出IIII,此时线程上的同步任务全部执行结束。
    在执行任务队列中的异步任务时,微任务优先于宏任务执行,所以先执行微任务 t2 打印输出 HHHH,然后执行宏任务 t1 打印输出 FFFF
    所以综上 结果输出是 DDDD AAAA CCCC BBBB GGGG IIII HHHH FFFF

    第三题:写出以下代码的运行结果,共有七小问。

    问题1

    async function t1() {
      let a = await "mango";
      console.log(a);
    }
    t1()
    

    答案解析:
    await是一个表达式,如果后面不是一个promise对象,就直接返回对应的值。
    所以问题1可以理解为

    async function t1() {
     let a = "mango";
     console.log(a);//mango
    }
    t1()
    

    问题2

    async function t2() {
      let a = await new Promise((resolve) => {});
      console.log(a);//
    }
    t2()
    

    答案解析:
    await后面如果跟一个promise对象,await将等待这个promise对象的resolve状态的值value,且将这个值返回给前面的变量,此时的promise对象的状态是一个pending状态,没有resolve状态值,所以什么也打印不了。

    问题3

    async function t3() {
      let a = await new Promise((resolve) => {
        resolve();
      });
      console.log(a);//undefined
    }
    t3()
    

    答案解析:
    await后面如果跟一个promise对象,await将等待这个promise对象的resolve状态的值value,且将这个值返回给前面的变量,此时的promise对象的状态是一个resolve状态,但是它的状态值是undefined,所以打印出undefined。

    问题4

    async function t4() {
      let a = await new Promise((resolve) => {
        resolve("hello");
      });
      console.log(a);//hello
    }
    t4()
    

    答案解析:
    await后面如果跟一个promise对象,await将等待这个promise对象的resolve状态的值,且将这个值返回给前面的变量,此时的promise对象的状态是一个resolve状态,它的状态值是hello,所以打印出hello。

    问题5

    async function t5() {
      let a = await new Promise((resolve) => {
        resolve("hello");
      }).then(() => {
        return "lala";
      });
      console.log(a);//lala
    }
    t5()
    

    答案解析:
    await后面如果跟一个promise对象,await将等待这个promise对象的resolve状态的值,且将这个值返回给前面的变量,此时的promise对象的状态是一个resolve状态,它的状态值是hello,紧接着后面又执行了一个then方法,then方法又会返回一个全新的promise对象,且这个then方法中的返回值会作为这个全新的promise中resolve的值,所以最终的结果是lala。

    问题6

    async function t6() {
      let a = await fn().then((res)=>{return res})
      console.log(a);//undefined
    }
    async function fn(){
        await new Promise((resolve)=>{
            resolve("mango")
        })
    }
    t6()
    

    答案解析:
    async函数执行返回一个promise对象,且async函数内部的返回值会当作这个promise对象resolve状态的值

    async function fn() {
      return "la";
    }
    var p = fn();
    console.log(p); //Promise {<resolved>: "la"}
                //__proto__: Promise
                           //[[PromiseStatus]]: "resolved"
                          //[[PromiseValue]]: "la"
    

    首先考虑 fn() 执行返回一个promise对象,因为fn执行没有返回值,所以这个promise对象的状态resolve的值是undefined,且将这个undefined当作下一个then中回调函数的参数,所以打印的结果是undefined

    问题7

    async function t7() {
      let a = await fn().then((res)=>{return res})
      console.log(a);
    }
    async function fn(){
        await new Promise((resolve)=>{
            resolve("mango")
        })
        return "lala"
    }
    t7()
    

    答案解析:
    首先考虑 fn() 执行返回一个promise对象,因为fn()执行有返回值lala,所以这个promise对象的状态resolve的值是lala,且将这个lala当作下一个then中回调函数的参数,所以打印的结果是lala。

    注意细节

    • async函数执行的返回结果是一个promise对象,这个函数的返回值是这个promise状态值resolve的值

    • await后面如果不是一个promise对象,将直接返回这个值

    • await后面如果是一个promise对象,将会把这个promise的状态resolve的值返回出去。

      以上没有考虑reject状态。

    第四题:谈一谈下面两种写法的区别:

    //第一种
    promise.then((res) => {
        console.log('then:', res)
    }).catch((err) => {
        console.log('catch:', err)
    })
    //第二种
    promise.then((res) => {
        console.log('then:', res)
    }, (err) => {
        console.log('catch:', err)
    })
    

    答案解析:
    第一种catch方法可以捕获到catch之前的整条链路上抛出的异常。
    第二种then方法的第二个参数捕获的异常依赖于上一个Promise对象的执行结果。
    promise.then(seccessCb,fallCb)接收两个函数作为参数,来处理上一个promise对象的结果。then方法返回的是promise对象。
    第一种链式写法,使用catch,相当于给前一个then方法返回的promise注册回调,可以捕获到前面then没有被处理的异常。
    第二种是回调的写法,仅为上一个promise注册异常回调。
    如果是promise内部报错reject抛出错误后,then的第二个参数就能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。
    如果是then的第一个参数函数resolve中抛出了异常,即成功回调函数出现异常后,then的第二个参数reject捕获不到,但是catch方法可以捕获到。

    第五题:根据题目写出符合要求的代码

    var urls = [
      'http://jsonplaceholder.typicode.com/posts/1',
      'http://jsonplaceholder.typicode.com/posts/2', 
      'http://jsonplaceholder.typicode.com/posts/3', 
      'http://jsonplaceholder.typicode.com/posts/4',
      'http://jsonplaceholder.typicode.com/posts/5', 
      'http://jsonplaceholder.typicode.com/posts/6', 
      'http://jsonplaceholder.typicode.com/posts/7', 
      'http://jsonplaceholder.typicode.com/posts/8',
      'http://jsonplaceholder.typicode.com/posts/9', 
      'http://jsonplaceholder.typicode.com/posts/10'
    ]
    
    function loadDate (url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.onload = function () {
          resolve(xhr.responseText)
        }
        xhr.open('GET', url)
        xhr.send()
      })
    }
    

    在 urls 数组中存放了 10 个接口地址。同时还定义了一个 loadDate 函数,这个函数接受一个 url 参数,返回一个 Promise 对象,该 Promise 在接口调用成功时返回 resolve,失败时返回 reject。
    要求:任意时刻,同时下载的链接数量不可以超过 3 个。 试写出一段代码实现这个需求,要求尽可能快速地将所有接口中的数据得到。

    答案解析:
    按照题意我们可以这样做,首先并发请求 3 个 url 中的数据,当其中一条 url 请求得到数据后,立即发起对一条新 url 上数据的请求,我们要始终让并发数保持在 3 个,直到所有需要加载数据的 url 全部都完成请求并得到数据。
    用 Promise 实现的思路就是,首先并发请求3个 url ,得到 3 个 Promise ,然后组成一个叫 promises 的数组。再不断的调用 Promise.race 来返回最快改变状态的 Promise ,然后从数组promises中删掉这个 Promise 对象,再加入一个新的 Promise,直到所有的 url 被取完,最后再使用 Promise.all 来处理一遍数组promises中没有改变状态的 Promise。

    var urls = [
      'http://jsonplaceholder.typicode.com/posts/1',
      'http://jsonplaceholder.typicode.com/posts/2', 
      'http://jsonplaceholder.typicode.com/posts/3', 
      'http://jsonplaceholder.typicode.com/posts/4',
      'http://jsonplaceholder.typicode.com/posts/5', 
      'http://jsonplaceholder.typicode.com/posts/6', 
      'http://jsonplaceholder.typicode.com/posts/7', 
      'http://jsonplaceholder.typicode.com/posts/8',
      'http://jsonplaceholder.typicode.com/posts/9', 
      'http://jsonplaceholder.typicode.com/posts/10'
    ]
    
    function loadDate (url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.onload = function () {
          resolve(xhr.responseText)
        }
        xhr.open('GET', url)
        xhr.send()
      })
    }
    
    function limitLoad(urls, handler, limit) {
        // 对数组进行一个拷贝
        const sequence = [].concat(urls)
        let promises = [];
    
        //实现并发请求达到最大值
        promises = sequence.splice(0, limit).map((url, index) => {
            // 这里返回的 index 是任务在数组 promises 的脚标
            //用于在 Promise.race 后找到完成的任务脚标
            return handler(url).then(() => {
                return index
            }); 
        });
    
        // 利用数组的 reduce 方法来以队列的形式执行
        return sequence.reduce((last, url, currentIndex) => {
            return last.then(() => {
                // 返回最快改变状态的 Promise
                return Promise.race(promises)
            }).catch(err => {
                // 这里的 catch 不仅用来捕获前面 then 方法抛出的错误
                // 更重要的是防止中断整个链式调用
                console.error(err)
            }).then((res) => {
                // 用新的 Promise 替换掉最快改变状态的 Promise
                promises[res] = handler(sequence[currentIndex]).then(
                    () => { return res });
            })
        }, Promise.resolve()).then(() => {
            return Promise.all(promises)
        })
        
    }
    limitLoad(urls, loadDate, 3)
    
    /*
    因为 loadDate 函数也返回一个 Promise
    所以当 所有图片加载完成后可以继续链式调用
    
    limitLoad(urls, loadDate, 3).then(() => {
        console.log('所有url数据请求成功');
    }).catch(err => {
        console.error(err);
    })
    */
    

    相关文章

      网友评论

        本文标题:前端笔试题(异步专项练习)

        本文链接:https://www.haomeiwen.com/subject/qmalpktx.html