第一题:阅读下面代码,我们只考虑浏览器环境下的输出结果,写出它们结果打印的先后顺序,并分析出原因。
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);
})
*/