面试题 - 六种前端异步处理方案汇总

面试题 - 六种前端异步处理方案汇总

背景

1. 单线程机制

js是一门单线程语言,即一次只能完成一个任务。

每个任务都必须按顺序执行,并且一个任务执行结束后,才可以继续执行下一个任务。

TaskA --> TaskB --> TaskC

js虽然是单线程语言,但是js运行在浏览器上,浏览器本身是多线程的,主要包含以下几个线程:

  • GUI 渲染线程 ★
  • JavaScript 引擎线程 ★
  • 定时触发器线程 / EventLoop轮询处理线程
  • 浏览器事件触发线程
  • 异步http请求线程 ★

2. 单线程产生的问题

当一个任务需要大量时间开销的情况下,容易造成浏览器’假死’现象。从用户角度来看,整个程序像是已经停止运行。

3. 解决方案

为解决阻塞问题,浏览器允许异步执行一些操作,所以产生了异步操作的感念。

4. 同步JS和异步JS

  • 同步阻塞:A调用B,B处理获得结果,才返回给A。A在这个过程中,一直等待B的处理结果,没有拿到结果之前,需要A(调用者)一直等待和确认调用结果是否返回,拿到结果,然后继续往下执行。 做一件事,没有拿到结果之前,就一直在这等着,一直等到有结果了,再去做下边的事情

    一次只能在一个主线程上发生一件事情,其他所有事情都将被阻塞,直到操作完成

    // 同步操作
    function mainThread(){
        console.log('打印A');
        console.log('打印B');
        let date;
        for (let i = 0 ; i < 10000000 ; i++){
            let dateitem = new Date();
            date = dateitem
        }
        console.log('打印最新的日期');
        console.log(date);
        console.log('打印C');
        let pElem = document.createElement('p');
        pElem.textContent = 'This is a newly-added paragraph.';
        document.body.appendChild(pElem);
        console.log('This is a newly-added paragraph');
        console.log('打印D');
    }
    mainThread()
    
  • 异步非阻塞:A调用B,无需等待B的结果,B通过状态,通知等来通知A或回调函数来处理。做一件事,不用等待事情的结果,然后就去忙别的了,有了结果,再通过状态来告诉我,或者通过回调函数来处理。

    将耗时任务放在其他线程中执行,主线程在将来某个时刻收到回调函数的通知

    // 回调函数处理里布操作,将耗时较长的操作作为异步操作,不影响界面的渲染
    function asyncCB(callBack) {
        setTimeout( function () {
            let data;
            for (let i = 0 ; i < 10000000 ; i++){
                let dateItem = new Date();
                data = dateItem;
            }
            callBack(data);
        } , 0)
    }
    // 正常顺序打印数据,并打印出最新的日期
    function mainThread(){
        console.log('打印A');
        console.log('打印B');
        // 使用回调函数 - 此时不会阻塞后面函数后面代码的执行
        asyncCB(function (value) {
            console.log("获取最新的数据");
            console.log(value);
        });
        // 直接打印CD
        console.log('打印C');
        console.log('打印D');
        let pElem = document.createElement('p');
        pElem.textContent = 'This is a newly-added paragraph.';
        document.body.appendChild(pElem);
        console.log('This is a newly-added paragraph');
    }
    

传统解决方案

以下演示实例的需求背景为:获取北京的天气情况,获取成功后再次获取上海的天气情况,获取成功后再次获取其他城市的天气情况。从而达到异步场景的模拟

1 - 回调函数

实现思路

函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

演示实例

function getCityWeather(url , callBack) {
    let xhr = new XMLHttpRequest();
    xhr.open('GET',url);
    xhr.send();
    xhr.onreadystatechange = function () {
        if (this.readyState !== 4) return ;
        if (this.status === 200){
            let resData = JSON.parse(this.response);
            console.log(resData);
            // 使用回调函数达到异步处理的效果
            if (callBack) callBack();
        }
    }
}
// 需求:依次输出北京、上海、天津的天气情况
getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz', function () {
    getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',function () {
        getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津')
    })
});

优缺点

  • 优点:思路简单,容易实现
  • 缺点:各个函数之间耦合性极高,流程混乱,后期不易维护。当还有多层回调函数时,容易形成回调函数地狱。
2 - 事件触发

实现思路

将 方案1 中的回调函数事件化,触发事件执行回调函数,原理与回调函数一致。事件处理在浏览器中本身就是一种异步机制,在事件发生时再触发函数。

优缺点

  • 优点:同回调函数一致,思路简单,容易实现
  • 缺点:使用起来不方便,每次都要手动的触发和绑定事件

ES6+ 解决方案

1 - Promise

实现思路

实例化一个promise对象,在promise对象中执行主函数,执行成功后resolve结果,执行失败后reject结果。即将回调函数的嵌套模式改为链式调用。

演示实例

单次链式调用

// 实例化一个Promise对象
let promise = new Promise(function (resolve , reject) {
    let xhr = new XMLHttpRequest();
    xhr.open('GET' , 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
    xhr.send();
    xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) return ;
        if (xhr.status === 200)  return resolve(xhr.response);
        reject()
    }
});
// 成功之后执行函数
promise.then(function (value) {
    let xhr2 = new XMLHttpRequest();
    xhr2.open('GET' , 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
    xhr2.send();
    xhr2.onreadystatechange = function () {
        if (xhr2.readyState !== 4) return ;
        if (xhr2.status === 200){
            const data1 = JSON.parse(value);
            const data2 = JSON.parse(this.response);
            console.log(data1);
            console.log(data2);
        }
    }
}).catch((error) =>{
    console.log(error);
})

多次链式调用

// promise对象
function getJSON(url , data){
    return new Promise(((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET',url);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept' , 'application/json');
        xhr.send();
        xhr.onreadystatechange = function () {
            if (this.readyState !== 4) return ;
            if (this.status === 200){
                if (data){
                    console.log("上次执行结果");
                    console.log(data);
                }
                return resolve(this.response);
            }
            reject(new Error(this.statusText));
        }
    }))
}
// 链式调用
getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz')
    .then(function (value) {
    return getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',value);
})
    .then(function (value) {
    return getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=河北',value);
})
    .then(function (value) {
    return  getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=山东',value);
})
    .catch((error) => {
    console.log(error);
})

优缺点

  • 优点:使回调函数变为链式操作,逻辑更加清晰,执行顺序更明了
  • 缺点:代码冗余,主要代码都放在了promise构造函数和then回调中,主体代码不明确
2 - gengerator 函数

generator实现异步操作 - 手动执行

// 使用 Generator 封装一个异步操作
function* getCityWeather () {
    let url = 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=北京';
    let result = yield fetch(url);
    document.querySelector('#cityName').innerHTML = result.city;
    document.querySelector('#airTips').innerHTML = result.air_tips;
}

// 执行函数s
let gen = getCityWeather();
let response = gen.next();
response.value.then((data)=>{
    return data.json();
}).then((myJson) => {
    console.log(myJson);
    gen.next(myJson)
})
generator-thunk

关于thunk函数

  • Thunk 函数是自动执行Generator函数的一种方法;
  • Thunk 函数是将多参数函数转换为只接受回调函数的单参数函数
  • 使用的目的是为了能够自动控制Generator函数的流程,接受和交还程序的执行权

演示案例

// thunk函数
function thunk(url) {
    return function (fn) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET',url);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept' , 'application/json');
        xhr.send();
        xhr.onreadystatechange = function () {
            let data , error ;
            if (this.readyState !== 4) return ;
            if (this.status === 200){
                data = this.response;
            }else {
                error = new Error(this.statusText);
            }
            fn(error,data);
        }
    }
}

// 声明异步调用函数
function* generator() {
    let data1 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
    console.log(data1);
    let data2 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
    console.log(data2)
    let data3 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津');
    console.log(data3);
}

// 手动执行函数
let g = generator();
g.next().value((error , data) =>{
    if (error) return g.throw(error);
    g.next(data).value((error , data) => {
        if (error) return g.throw(error);
        g.next(data).value((error , data) => {
            if (error) return g.throw(error);
            g.next(data);
        });
    })
});

// 自动执行
function autoRun(gen) {
    const g = gen();
    function next(error , data) {
        if (error) return g.throw(error);
        const res = g.next(data);
        if (res.done) return ;
        res.value(next);
    }
    next();
}
autoRun(generator);
generator-promise

演示案例

// promise函数
function pm(url) {
    return new Promise(((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET',url);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept' , 'application/json');
        xhr.send();
        xhr.onreadystatechange = function () {
            if (this.readyState !== 4) return ;
            if (this.status === 200){
                resolve(this.response);
            }else {
                reject(new Error(this.statusText));
            }
        }
    }))
}

// 声明异步函数
function* generator() {
    let data1 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
    let data2 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
    let data3 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津');
    console.log(data1);
    console.log(data2);
    console.log(data3);
}

// 手动执行函数
const g = generator();
g.next().value.then((data) => {
    g.next(data).value.then((data) => {
        g.next(data).value.then((data) => {
            g.next(data);
        })
    })
}).catch((error) => {
    console.log(error);
})

// 自动执行函数
function autoRun(gen) {
    const g = gen();
    function next(data) {
        const res = g.next(data);
        if (res.done) return;
        res.value.then(next);
    }
    next();
}
autoRun(generator);
3 - async 和 await

演示案例

function getJSON(url , data){
    return new Promise(((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET',url);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept' , 'application/json');
        xhr.send();
        xhr.onreadystatechange = function () {
            if (this.readyState !== 4) return ;
            if (this.status === 200){
                if (data){
                    console.log("上次执行结果");
                    console.log(data);
                }
                return resolve(this.response);
            }
            reject(new Error(this.statusText));
        }
    }))
}

// 声明 async 函数
async function getWeather() {
    const data1 = await getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
    const data2 = await getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',data1);
    console.log(data1);
    console.log(data2);
    console.log("执行进行中")
    return '执行结束'
}

// 执行函数
getWeather().then((resolve) => {
    console.log(resolve);
});

相关演示代码下载链接

  • https://github.com/jianyaoo/Front-end-demo/tree/master/ES6/

参考链接

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《2021测试面试题 - pdf》是一本面试题集合,以PDF格式呈现。这本书可能包含了各种不同类型的面试题,涵盖了不同领域和层级的职位。 阅读这本书可以提供一些关于面试准备的指导,帮助应聘者了解常见的面试问题,以及如何回答这些问题。它可以帮助应聘者熟悉面试过程,了解面试官的期望,并为他们的面试做好准备。 这本书可能包含常见的面试问题,例如个人背景介绍、职业发展规划、技术能力、解决问题的能力、团队合作等。通过阅读这些问题,应聘者可以思考如何回答,并准备一些典型的示例和故事来支持他们的回答。 这本书还可能提供一些面试技巧和建议,包括如何在面试中展示自己的能力和经验,如何有效地回答问题,如何展示个人的职业素养等。 总之,《2021测试面试题 - pdf》是一本面试题集合,旨在帮助应聘者准备面试,了解常见的面试问题,并提供一些面试技巧和建议。对于那些正在寻找工作或准备面试的人来说,这本书可能是一个有用的资源。 ### 回答2: 《2021测试面试题- pdf》是一本面试题集合,针对测试工程师岗位的招聘面试进行了整理和编写。这本题集包含了多种类型的测试题目,涵盖了软件测试的各个方面,旨在帮助招聘单位了解应聘者的测试知识和技能。 这本面试题集的内容包括但不限于测试基础知识、测试策略和方法、测试工具和框架、自动化测试、性能测试、安全测试等。这些题目有些是选择题,有些是简答题或编程题,都是经过精心设计的,可以帮助招聘单位全面地了解应聘者在测试领域的实际能力和经验。 对于应聘者而言,阅读和解答《2021测试面试题- pdf》可以帮助他们复习和巩固测试知识,并提前了解可能会在面试中遇到的问题和考察点。通过认真准备,应聘者可以在面试中更好地回答问题,展示自己的实际能力和经验,提高获得聘用的机会。 对于招聘单位而言,这本题集可以作为一个参考工具,用于筛选和评估应聘者。招聘单位可以根据《2021测试面试题- pdf》中的问题,结合自己的需求和要求,从中选取适合的问题进行面试,以衡量应聘者的能力和适应性。 综上所述,《2021测试面试题- pdf》是一本用于软件测试岗位招聘面试的题目集,旨在帮助招聘单位了解应聘者的测试知识和技能,同时也为应聘者提供了一个复习和准备面试的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值