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

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

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);
})
*/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容