跳到主内容

Javascript异步编程相关面试题

由于 JS 是单线程的,引入了事件循环,使其可以处理异步任务而不阻塞,为了避免回调地狱嵌套问题,提供了 Promise,async await,Generator 等三种处理异步流程控制的方法。本文整理了 JS 处理异步等常见问题。

promise 常见问题

如何一次执行多个异步请求?

  • 使用 Promise.all ,通过传入多个异步请求数组,如果所有请求都成功,则进入 fulfilled 状态,如果有一个失败,则立刻进入 rejected 状态。
  • 使用 Promise.allSettled ,也可以传入多个异步请求数组,所有的请求无论成功还是失败,都会进入 fulfilled 状态。

Promise 代码题

const promise1 = new Promise((resolve, reject) => {
console.log("promise1");
});
console.log("1", promise1);

上面代码执行的结果是啥?

点击查看结果

'promise1'
'1' Promise{<pending>}

使用 Promise 实现每间隔 1 秒输出 1,2,3

  • 使用 reduce 实现串行执行
  • 每次执行返回一个新的 Promise

其实使用 setTimeout 递归调用即可,出这道题纯属脱裤子放屁。

点击查看实现代码

const arr = [1, 2, 3];

function log(arr) {
arr.reduce((prev, curr) => {
return prev.then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(curr);
resolve();
}, 1000);
});
});
}, Promise.resolve());
}

log(arr);

实现 mergePromise 函数

实现 mergePromise 函数,异步函数顺序执行,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组 data 中。

const timeout = (ms) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});

const ajax1 = () =>
timeout(2000).then(() => {
console.log("1");
return 1;
});

const ajax2 = () =>
timeout(1000).then(() => {
console.log("2");
return 2;
});

const ajax3 = () =>
timeout(2000).then(() => {
console.log("3");
return 3;
});

const mergePromise = (ajaxArray) => {
// 在这里实现你的代码
};

mergePromise([ajax1, ajax2, ajax3]).then((data) => {
console.log("done");
console.log(data); // data 为 [1, 2, 3]
});

// 分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
点击查看实现代码

const mergePromise = (ajaxArray) => {
// 在这里实现你的代码
var data = [];
var sequence = Promise.resolve();
ajaxArray.forEach(function (item) {
sequence = sequence.then(item).then(function (res) {
data.push(res);
return data;
});
});
return sequence;
};

使用 Promise 实现红绿灯交替重复亮

红灯 3 秒亮一次,黄灯 2 秒亮一次,绿灯 1 秒亮一次;如何让三个灯不断交替重复亮灯?

(用 Promise 实现)三个亮灯函数代码如下:

function red() {
console.log("red");
}
function green() {
console.log("green");
}
function yellow() {
console.log("yellow");
}

function light(cb, timer) {
return new Promise((resolve) => {
setTimeout(() => {
cb();
resolve();
}, timer);
});
}
点击查看结果

function step() {
Promise.resolve()
.then(() => {
return light(red, 3000);
})
.then(() => {
return light(green, 2000);
})
.then(() => {
return light(yellow, 1000);
})
.finally(() => {
return step();
});
}

限制异步操作的并发个数并尽可能快的完成全部

有 8 个图片资源的 url,已经存储在数组 urls 中。

urls 类似于

['https://image1.png', 'https://image2.png', ....]

假设已经有一个函数 loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject

var urls = ['https://image1.png', 'https://image2.png', ....];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
console.log("图片加载完成");
resolve();
};
img.onerror = reject;
img.src = url;
});
}

但有一个要求,任何时刻同时下载的链接数量不可以超过 3 个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

点击查看结果

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, loadImg, 3)
.then(() => {
console.log("所有图片加载完成");
})
.catch((err) => {
console.error(err);
});