美文网首页
前端异步面试题大全

前端异步面试题大全

作者: Aniugel | 来源:发表于2020-03-02 18:09 被阅读0次

    以下题目是根据网上多份面经收集而来的,题目相同意味着被问的频率比较高(x3表示有三份面经被问),有问题欢迎留言讨论,喜欢可以点赞关注

    1、浏览器架构

    1.用户界面
    2.浏览器引擎(负责窗口管理、Tab进程管理等)
    3.渲染引擎(有叫内核,负责HTML解析、页面渲染)
    4.JS引擎(JS解释器,如Chrome和Nodejs采用的V8)

    2、JS异步解决方案的发展历程以及优缺点

    https://blog.csdn.net/lunahaijiao/article/details/87167417
    1、回调函数(callback)

    setTimeout(() => {
        // callback 函数体
    }, 1000)
    

    缺点:回调地狱,不能用 try catch 捕获错误,不能 return
    优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

    2. Promise
    Promise就是为了解决callback的问题而产生的。
    Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
    优点:解决了回调地狱的问题

    ajax('XXX1')
      .then(res => {
          // 操作逻辑
          return ajax('XXX2')
      }).then(res => {
          // 操作逻辑
          return ajax('XXX3')
      }).then(res => {
          // 操作逻辑
      })
    

    缺点:无法取消 Promise ,错误需要通过回调函数来捕获

    3. Generator
    特点:可以控制函数的执行,可以配合 co 函数库使用

    function *fetch() {
        yield ajax('XXX1', () => {})
        yield ajax('XXX2', () => {})
        yield ajax('XXX3', () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    

    4. Async/await
    async、await 是异步的终极解决方案
    优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

    async function test() {
      // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
      // 如果有依赖性的话,其实就是解决回调地狱的例子了
      await fetch('XXX1')
      await fetch('XXX2')
      await fetch('XXX3')
    }
    

    下面来看一个使用 await 的例子:

    let a = 0
    let b = async () => {
      a = a + await 10
      console.log('2', a) // -> '2' 10
    }
    b()
    a++
    console.log('1', a) // -> '1' 1
    

    对于以上代码你可能会有疑惑,让我来解释下原因

    首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
    因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
    同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
    上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

    3、介绍各种异步方案x3

    同2

    4、JS执行过程中分为哪些阶段

    https://blog.csdn.net/weixin_34009794/article/details/91431985

    1、语法分析 2、预编译阶段 3、执行阶段

    一. 语法分析
    分析该js脚本代码块的语法是否正确,如果出现不正确,则向外抛出一个语法错误(SyntaxError),停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段。下面阶段的代码执行不会再进行语法校验,语法分析在代码块加载完毕时统一检验语法。

    二. 预编译阶段

    1、js的运行环境
    每进入一个不同的运行环境都会创建一个相应的执行上下文(Execution Context),那么在一段JS程序中一般都会创建多个执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈(call stack),栈底永远是全局执行上下文(Global Execution Context),栈顶则永远是当前执行上下文。

    2、函数调用栈/执行栈
    调用栈,也叫执行栈,具有LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。

    3、执行上下文的创建
    执行上下文可理解为当前的执行环境,与该运行环境相对应,具体分类如上面所说分为全局执行上下文和函数执行上下文。创建执行上下文的三部曲:①创建变量对象(Variable Object)②建立作用域链(Scope Chain)③确定this的指向

    三. 执行阶段

    1. 网页的线程
    2. 宏任务
    3. 事件循环
    4. 微任务
    5、定时器的执行顺序

    长话短说,我们需要记住的是:因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。 所以即使把定时器的时间设置为0还是会先执行当前的一些代码。划重点:简单来说就是浏览器会限制性当前代码,等当前的代码执行完了,会看一下有没有待执行的事件,如果有才执行定时器的代码

    6、同步与异步的执行顺序

    1、不管是同步还是异步,js都会按顺序执行,只是不等待异步的执行结果而已(并不是遇到异步的就绕过不执行,别蒙了)
    2、同步的任务没有优先级之分,异步执行有优先级,先执行微任务(microtask队列),再执行宏任务(macrotask队列),同级别按顺序执行

    7、如何处理异常捕获

    https://juejin.im/post/5cc15de5e51d456e68659340

    JS中的异常捕获(目的:把抛出的错误捕获到,不让其阻断浏览器的继续执行)一般写法如下:

    try{
        //需要执行的JS代码(可能会报错)
    }catch(e){
        //try中代码报错,会执行catch
    }finally{
        //不管try中的代码成功还是失败都会执行
    }
    

    但是宏任务的回调函数中的错误无法捕获

        function main() {
            try {
                setTimeout(() => {
                    throw new Error('async error')
                }, 1000)
            } catch (e) {
                console.log(e, 'err')
            }
        }
        main();
    
    8、setInterval需要注意的点

    window.setInterval(para1,para2);
    一共有以下几种形式:
    window.setInterval(function(){alert('xxx')},1000); para1为匿名函数的形式,
    window.setInterval("myFunc()",1000); para1为一个字符串,而且这个字符串是一个已经写好的函数的名称。
    以上这两种可以正常运行,
    1、第一个参数必须是一个方法且必须加引号,否则只会执行一次
    2、不能传递带参数的函数
    如:setInterval(function(args), 600);
    3、setInterval 周期性的调用函数或计算方法,关闭用clearInterval 。setInterval 和clearInterval 是一对一的关系。比如想要对同一个按钮在不同场景中,使用周期性的调用不同的函数,那么需要先关掉上一个setInterval,再设定另一个setInterval不然上一个setInterval仍然在进行着。

    9、定时器为什么是不精确的

    因为定时器是异步的,要等到同步任务执行完之后,才会去执行异步的任务,即使setTimeout(0)中时间为0也不是立马执行。再者w3c在HTML标准中规定,要求setTimeout时间低于4ms的都按4ms来算。

    解决方法: 使用 web Worker 将定时函数作为独立线程执行

    10、setTimeout(1)和setTimeout(2)之间的区别

    返回一个 ID(数字),可以将这个ID传递给 clearTimeout() 来取消执行。setTimeout(1)的id数比setTimeout(2)的id数要大1

    11、如何解决同步调用代码耗时太高的问题

    ①异步处理②web worker 开辟线程处理

    12、异步请求,低版本fetch如何低版本适配

    https://www.cnblogs.com/wonyun/p/fetch_polyfill_timeout_jsonp_cookie_progress.html

    说道fetch就不得不提XMLHttpRequest了,XHR在发送web请求时需要开发者配置相关请求信息和成功后的回调,尽管开发者只关心请求成功后的业务处理,但是也要配置其他繁琐内容,导致配置和调用比较混乱,也不符合关注分离的原则;fetch的出现正是为了解决XHR存在的这些问题。例如下面代码:

    fetch(url).then(function(response) {
      return response.json();
    }).then(function(data) {
      console.log(data);
    }).catch(function(e) {
      console.log("Oops, error");
    });
    

    上面这段代码让开发者只关注请求成功后的业务逻辑处理,其他的不用关心,相当简单;也比较符合现代Promise形式,比较友好。fetch是基于Promise设计的,从上面代码也能看得出来,这就要求fetch要配合Promise一起使用。正是这种设计,fetch所带来的优点正如传统 Ajax 已死,Fetch 永生总结的一样:

    • 语法简单,更加语义化
    • 基于标准的Promise实现,支持async/await
    • 使用isomorphic-fetch可以方便同构

    不过话说回来,fetch虽然有很多优点,但是使用fetch来进行项目开发时,也是有一些常见问题的,下面就来说说fetch使用的常见问题。

    1、fetch兼容性
    在各个浏览器低版本的情况下都是不被支持的。那么问题来了,如何在所有浏览器中通用fetch呢,当然就要考虑fetch的polyfill了。上面说过,fetch是基于Promise来实现的,所以在低版本浏览器中Promise可能也未被原生支持,所以还需要Promise的polyfill;大多数情况下,实现fetch的polyfill需要涉及到的:* promise的polyfill,例如es6-promise、babel-polyfill提供的promise实现。* fetch的polyfill实现,例如isomorphic-fetch和whatwg-fetch 这样是否就可以安全的使用fetch来进行前后端通信了?上面说了在大多数情况下是这样,但是IE8/9则比较特殊:IE8它使用的是ES3,而IE9则对ES5部分支持。这种情况下还需要ES5的polyfill es5-shim支持了。

    2、fetch默认不携带cookie
    若要fetch请求携带cookie信息,只需设置一下credentials选项即可,例如fetch(url, {credentials: 'include'});

    3、fetch请求对某些错误http状态不会reject
    这主要是由fetch返回promise导致的,因为fetch返回的promise在某些错误的http状态下如400、500等不会reject,相反它会被resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;所以一般会对fetch请求做一层封装,例如下面代码所示:

    4、fetch不支持超时timeout处理
    用过fetch的都知道,fetch不像大多数ajax库那样对请求设置超时timeout,它没有有关请求超时的feature,这一点比较蛋疼。所以在fetch标准添加超时feature之前,都需要polyfill该特性。

    5、fetch不支持JSONP

    13、JS怎么实现异步

    1、回调函数(定时器、事件监听)
    2、Promises对象 generator async/await
    3、发布订阅者模式

    14、异步整个执行周期
    image.png
    15、JS为什么要区分微任务和宏任务
    16、介绍下Promise,内部实现x3

    是ES6标准的异步编程的一种解决方案,解决回调地狱

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    17、手写Promise实现x3

    https://juejin.im/post/5d0da5c8e51d455ca0436271

    18、介绍下Promise的用途和性质

    用途
    是ES6标准的异步编程的一种解决方案,解决回调地狱

    性质
    1,promise内部分微任务和宏任务
    2,promise本身是同步的,但是他的成功的回调.then方法里面是异步的
    3,promise的状态是不可逆的
    4,then return出去的值,会被后面的then接收,如果后面还有跟then的话,catch同理
    4,promise不管返回什么值,都会被包装成一个promise对象,即使这个返回值是error
    5,then接收到的值,如果不是一个函数,会穿透到后面的then
    5,promise对象如果resolve或者reject的也是一个promise对象,那么promise对象的状态会由reslove或者reject的promise对象的状态决定。

    19、介绍Promise,异常捕获

    ①第一种单独对 .then() 中指定异常处理函数(第一种一般用在,希望捕获异常然后不影响接下里Promise的执行)我们只需要在 .then() 中添加两个function就好了,第二个是用来处理失败的情况。
    ②第二种使用.catch来实现全部捕获(第二种一般用在,当一个Promise发生了异常,剩下的Promise都不在执行)

    一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

    代码因为没有报错,会跳过了catch方法,直接执行后面的then方法。此时,要是then方法里面报错,就与前面的catch无关了。可以用第二个catch方法用来捕获前一个catch或者then方法抛出的错误。

    Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

    getJSON('/posts.json').then(function(posts) {
      // ...
    }).catch(function(error) {
      // 处理 getJSON 和 前一个回调函数运行时发生的错误
      console.log('发生错误!', error);
    });
    

    上面代码中,getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

    p.then((val) => console.log('fulfilled:', val))
      .catch((err) => console.log('rejected', err));
    
    // 等同于
    p.then((val) => console.log('fulfilled:', val))
      .then(null, (err) => console.log("rejected:", err));
    

    如果 Promise 状态已经变成resolved,再抛出错误是无效的。

    const promise = new Promise(function(resolve, reject) {
      resolve('ok');
      throw new Error('test');
    });
    promise
      .then(function(value) { console.log(value) })
      .catch(function(error) { console.log(error) });
    // ok
    

    上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

    getJSON('/post/1.json').then(function(post) {
      return getJSON(post.commentURL);
    }).then(function(comments) {
      // some code
    }).catch(function(error) {
      // 处理前面三个Promise产生的错误
    });
    
    20、如何实现 Promise.finally ?

    finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

    promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
    

    上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。finally本质上是then方法的特例。

    promise
    .finally(() => {
      // 语句
    });
    
    // 等同于
    promise
    .then(
      result => {
        // 语句
        return result;
      },
      error => {
        // 语句
        throw error;
      }
    );
    

    实现

    Promise.prototype.finally = function (callback) {
      let P = this.constructor;
      return this.then(
        value  => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => { throw reason })
      );
    };
    
    21、实现promise.all和原理x2

    Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

    const p = Promise.all([p1, p2, p3]);
    上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

    p的状态由p1、p2、p3决定,分成两种情况。
    

    (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

    (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    下面是一个具体的例子。

    // 生成一个Promise对象的数组
    const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
      return getJSON('/post/' + id + ".json");
    });
    
    Promise.all(promises).then(function (posts) {
      // ...
    }).catch(function(reason){
      // ...
    });
    

    上面代码中,promises是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

    实现

    function isPromise(obj) {
        return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';  
    }
    
    const myPromiseAll = (arr)=>{
        let result = [];
        return new Promise((resolve,reject)=>{
            for(let i = 0;i < arr.length;i++){
                if(isPromise(arr[i])){
                    arr[i].then((data)=>{
                        result[i] = data;
                        if(result.length === arr.length){
                            resolve(result)
                        }
                    },reject)
                }else{
                    result[i] = arr[i];
                }
            }    
        })
    }
    
    22、实现promise.retry
    Promise.retry = function(getData, times, delay) {
        return new Promise((resolve, reject) => {
            function attemp() {
                getData().then((data) => {
                    resolve(data)
                }).catch((err) => {
                    if (times === 0) {
                        reject(err)
                    } else {
                        times--
                        setTimeout(attemp, delay)
                    }
                })
            }
            attemp()
        })
    }
    
    23、介绍Promise和then,promise里面和then里面执行有什么区别x2

    当我们在构造Promise的时候,构造函数内部的代码是立即执行的,then是异步的

    new Promise((resolve, reject) => {
    resolve('success')
    console.log('new Promise')
    })
    console.log('finifsh')
    // new Promise -> finifsh
    

    Promise实现了链式调用,也就是说每次调用then之后返回的都是一个Promise,并且是一个全新的Promise,原因也是因为状态不可变。如果你在then中 使用了return,那么return的值会被Promise.resolve()包装

    Promise.resolve(1)
      .then(res => {
        console.log(res) // => 1
        return 2 // 包装成 Promise.resolve(2)
      })
      .then(res => {
        console.log(res) // => 2
      })
    
    24、Promise 和 async/await 和 callback 、Generator的区别x3

    Promise
    promise比较简单,也是最常用的,主要就是将原来的用回调函数的异步编程方法转成用relsove和reject触发事件, 用then和catch捕获成功或者失败的状态执行相应代码的异步编程的方法,由下面代码可以看出 promise将多个回调函数嵌套的回调地狱 ,变成了链式的写法 ,可读性更高写法也更清晰

    //回调写法
    function fun1(value,callback) {
        value++
        setTimeout(function() {
                value++
            setTimeout(function(){
                    value++
               setTimeout(function(){
                    console.log(value)
               },2000);
            },2000);
        ,2000);
    }
    fun1() // 4
    
    //Promise写法
    function fun1(value) {
        retrun  new Promise((resolve,reject)=> {
            setTimeout(function(callback){
                resolve(value++)
            },2000);
        }) 
    }
    
    fun1(0).then((value)=> {
        return  new Promise((resolve,reject)=> {
            setTimeout(function(callback){
                resolve(value++)
            },2000);
    }).then((value)=> {
        return  new Promise((resolve,reject)=> {
            setTimeout(function(callback){
                resolve(value++)
            },2000);
    }).then(value)=>{
        console.log(value) 
    }    // 4
    

    Generator函数
    Generator函数是将函数分步骤阻塞 ,只有主动调用next() 才能进行下一步 ,因为asyns函数相当于Generator函数的语法糖,做出了优化,所以这里对Generator函数不做赘述,而且一般用到异步编程的时候一般也只用async和promise。所以这里就省略了,这里改一下,因为dva中异步处理的实现就是Generator函数,所以还是挺有用的,当然dva中自己带状态机使函数自动运行下去。

    async函数
    简单的说async函数就相当于自执行的Generator函数,相当于自带一个状态机,在await的部分等待返回, 返回后自动执行下一步。而且相较于Promise,async的优越性就是把每次异步返回的结果从then中拿到最外层的方法中,不需要链式调用,只要用同步的写法就可以了。更加直观而且,更适合处理并发调用的问题。但是async必须以一个Promise对象开始 ,所以async通常是和Promise结合使用的

    //async函数写法
    function fun1(value) {
        retrun  new Promise((resolve,reject)=> {
            setTimeout(function(callback){
                resolve(value++)
            },2000);
        }) 
    }
    
    async function asy() {
    let v = 0
        v = await fun1(v)
        v = await fun1(v)
        v = await fun1(v)
        console.log(v)
    }  
    asy() //4
    

    从上面代码来看await函数的写法确实简洁了很多,而且异步的顺序也非常清晰。总的来说,async和generator函数主要就是为了解决异步的并发调用使用的 ,直接将参数从then里取出来,相比promise的链式调用,传参更加方便,异步顺序更加清晰

    另外: 我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。 其中setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行; promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。

    25、Promise有没有解决异步的问题(promise链是真正强大的地方)

    是ES6标准的异步编程的一种解决方案,promise主要是为了解决嵌套回调的问题(回调地狱),使代码更加简洁,promise将嵌套的回调函数改成.then()的链式使用

    首先通过new Promise(function)创建一个promise对象,接收一个函数参数,并且在函数中传入resolve以及reject两个参数;

    then() 接收两个函数,分别是对promise的resolve及reject状态处理的函数,并且处理结束之后返回promise对象

    all() 接收一个函数数组,进行并发操作,并将每个函数的结果以数组的形式返回

    race()接收函数数组,函数先执行完成之后先进入下一个回调函数中

    catch() 当then中出现错误时不会中止整个函数,catch能够获取到错误并进行提示

    26、promise 有几种状态, Promise 有什么特色和优缺点 x4

    等待中(pending) 完成了 (resolved) 拒绝了(rejected)

    Promise对象有以下两个特点。

    (1)对象的状态不受外界影响
    Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
    Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

    有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

    Promise也有一些缺点。
    ①无法取消Promise,一旦新建它就会立即执行,无法中途取消。
    ②如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
    ③当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择

    27、Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ? x3

    Promise构造函数是同步,then是异步

    Promise实例具有then方法,也就是说then方法时定义在原型对象上的。它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数(可选)。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)因此可以采用链式写法,即then方法后面再调用另一个then方法。采用链式的then可以指定一组按照次序调用的回调函数。这时,前一个回调函数可能返回一个还是Promise对象(即有异步操作),这时候一个回调函数就会等该Promise对象的状态发生变化,才会被调用。

    getJSON("/post/1.json").then(function(post) {
      return getJSON(post.commentURL);
    }).then(function (comments) {
      console.log("resolved: ", comments);
    }, function (err){
      console.log("rejected: ", err);
    });
    

    上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。

    如果采用箭头函数,上面的代码可以写得更简洁。

    getJSON("/post/1.json").then(
      post => getJSON(post.commentURL)
    ).then(
      comments => console.log("resolved: ", comments),
      err => console.log("rejected: ", err)
    );
    

    实现then处理

    // then方法接收两个参数,fn1,fn2,分别为Promise成功或失败后的回调
    Promise.prototype.then = function(fn1, fn2) {
      var self = this
      var promise2
    
      // 首先对入参 fn1, fn2做判断
      fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
      fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
    
      if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
            //todo
        })
      }
    
      if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
           //todo
        })
      }
    
      if (self.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
           // todo
        })
      }
    }
    
    28、Promise和setTimeout的区别(Event Loop)x3

    Promise构造函数中是立即执行(同步任务),then函数分发到微任务Event Queue(异步任务),setTimeout是分发到宏任务中

    29、将一个同步callback包装成promise形式

    本文要介绍的是如何将 callback 转换成 Promise,主要是写了一个具体的例子。首先请大家浏览一下这篇文章:
    如何把 Callback 接口包装成 Promise 接口

    接下来我遇到的问题是,如何把像 jquery 方式的 ajax 请求改成 promise 呢?因为 jquery 的 ajax 有两个回调函数:success 和 error 用来接收成功的请求和错误。那么,我们就可以根据上文的思路,在 success 和 error 中处理 promise 的消息就好了。于是就有了以下例子:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
      <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
    // 将 jquery 的 ajax 方法封装为 Promise 方法
    function post (url, params) {
      return new Promise(
        (resolve, reject) => {
          jQuery.ajax({
            url,
            data: {...params},
            type: "post",
            success: function (res) {
              resolve(res)
            },
            error: function (res) {
              reject(res)
            }
          }) 
        }
      )
    }
    
    // 异步请求,使用 then 和 catch
    function asyncPost () {
      console.log('==== 异步请求 start =====')
      post(
        'http://removeUrl',
        {
          foo: 'qwwerwer'
        }
      ).then(res => {
        console.log(res)
      }).catch(err => {
        console.log(err)
      })
      console.log('==== 异步请求 end =====')
    }
    
    // 同步请求,使用 async 和 await
    async function syncPost() {
      try {
        console.log('==== 同步请求 start =====')
        let res = await post(
          'http://removeUrl',
          {
            foo: 'qwwerwer'
          }
        )
        console.log(res)
        console.log('==== 同步请求 end =====')
      } catch (e) {
        console.log(e)
      }
    }
    </script>
    </head>
    <body>
      <button onclick="promisePost()">promisePost提交</button>
      <button onclick="asyncPost()">asyncPost提交</button>
    </body>
    </html>
    
    30、设计并实现 Promise.race()
    Promise._race = promises => new Promise((resolve, reject) => {
        promises.forEach(promise => {
            promise.then(resolve, reject)
        })
    })
    
    31、使用Async会注意哪些东西

    特点

    1. await只能放到async函数中
    2. 相比genrator语义化更强
    3. await后面可以是promise对象,也可以数字、字符串、布尔
    4. async函数总是返回是一个promise对象
    5. 只要await语句后面Promise状态变成 reject, 那么整个async函数会中断执行
      6.async函数和普通函数一样按顺序执行,同时,在执行到await语句时,返回一个Promise对象

    注意
    1)await 命令后面的Promise对象,运行结果可能是 rejected,此时等同于 async 函数返回的 Promise 对象被reject。因此需要加上错误处理,可以给每个 await 后的 Promise 增加 catch 方法;也可以将 await 的代码放在 try…catch 中。
    2)多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发,代码如下

    //下面两种写法都可以同时触发
    //法一
    async function f1() {
        await Promise.all([
            new Promise((resolve) => {
                setTimeout(resolve, 600);
            }),
            new Promise((resolve) => {
                setTimeout(resolve, 600);
            })
        ])
    }
    //法二
    async function f2() {
        let fn1 = new Promise((resolve) => {
                setTimeout(resolve, 800);
            });
        
        let fn2 = new Promise((resolve) => {
                setTimeout(resolve, 800);
            })
        await fn1;
        await fn2;
    }
    

    3)await命令只能用在async函数之中,如果用在普通函数,会报错
    4)async 函数可以保留运行堆栈
    实例代码:

    /
    * 函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。
    * 等到b()运行结束,可能a()早就* 运行结束了,b()所在的上下文环境已经消失了。
    * 如果b()或c()报错,错误堆栈将不包括a()。
    */
    function b() {
        return new Promise((resolve, reject) => {
            setTimeout(resolve, 200)
        });
    }
    function c() {
        throw Error(10);
    }
    const a = () => {
        b().then(() => c());
    };
    a();
    /**
    * 改成async函数
    */
    const m = async () => {
        await b();
        c();
    };
    m();
    
    32、Async里面有多个await请求,可以怎么优化(请求是否有依赖)
    33、对async、await的理解,内部原理以及实现

    1)async 函数是 Generator 函数的语法糖,使用 关键字 async 来表示,在函数内部使用 await 来表示异步
    2)ES7 提出的async 函数,终于让 JavaScript 对于异步操作有了终极解决方案
    3)async 作为一个关键字放到函数的前面,用于表示函数是一个异步函数,该函数的执行不会阻塞后面代码的执行
    4)await是等待,只能放到async函数里面,在后面放一个返回promise对象的表达式
    5)async和await是为了解决大量复杂不易读的Promise异步的问题

    async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

    1)内置执行器,Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
    2)更好的语义,async 和 await 相较于 * 和 yield 更加语义化,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await
    3)更广的适用性,co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象,而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)
    4)返回值是 Promise,async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用
    5)async方式,流程清晰,直观、语义明显,操作异步流程就如同操作同步流程, async 函数自带执行器,执行的时候无需手动加载。对于Promise的方式,如果处理流程复杂,整段代码将会充满then,不然很好的表示流程。对于Generator 方式,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行

    实现

    function my_co(it) {
        return new Promise((resolve, reject) => {
            function next(data) {
                try {
                    var { value, done } = it.next(data);
                }catch(e){
                    return reject(e);
                }
                if (!done) { 
                    //done为true,表示迭代完成
                    //value 不一定是 Promise,可能是一个普通值。使用 Promise.resolve 进行包装。
                    Promise.resolve(value).then(val => {
                        next(val);
                    }, reject);
                } else {
                    resolve(value);
                }
            }
            next(); //执行一次next
        });
    }
    function* test() {
        yield new Promise((resolve, reject) => {
            setTimeout(resolve, 100);
        });
        yield new Promise((resolve, reject) => {
            // throw Error(1);
            resolve(10)
        });
        yield 10;
        return 1000;
    }
    
    my_co(test()).then(data => {
        console.log(data); //输出1000
    }).catch((err) => {
        console.log('err: ', err);
    });
    
    
    34、Promise和Async处理失败的时候有什么区别

    Async/Await让try/catch可以同时处理同步和异步错误。在下面的promise示例中,try/catch不能处理JSON.parse的错误,因为它在Promise中。我们需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。

    const makeRequest = () => {
      try {
        getJSON()
          .then(result => {
            // JSON.parse可能会出错
            const data = JSON.parse(result)
            console.log(data)
          })
          // 取消注释,处理异步代码的错误
          // .catch((err) => {
          //   console.log(err)
          // })
      } catch (err) {
        console.log(err)
      }
    }
    

    使用aync/await的话,catch能处理JSON.parse错误:

    const makeRequest = async () => {
      try {
        // this parse may fail
        const data = JSON.parse(await getJSON())
        console.log(data)
      } catch (err) {
        console.log(err)
      }
    }
    

    相关文章

      网友评论

          本文标题:前端异步面试题大全

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