关于javascript:面试-JS-异步编程经典面试题

41次阅读

共计 8246 个字符,预计需要花费 21 分钟才能阅读完成。

原文地址

掘金

欢送 github star

大家好,我是林一一,异步编程在 JS 中是无奈防止的,也是面试必问的。本文应用通俗易懂的语言解析异步编程中的原理,开始浏览吧😁

思维导图

一、定时器

定时器:设定一个定时器,到了设定工夫,浏览器会把对应的办法执行。每一个定时器执行后都会有一个编号返回,每个定时器编号不一样。

1. 设定定时器

  • setTimeout([function], [interval])

    function 都是在达到设定工夫后才执行。且执行一次

    let count = 1
    let timer = setTimeout(function(){
        count++
        console.log(count)  // 2
    }, 1000)
    console.log(timer)  // 1
  • setInterval([function], [interval])

    在设定工夫内执行,不被动进行的状况下始终执行。

    let count = 1
    let timer = setInterval(function(){
        count++
        console.log(count)  // 2
    }, 1000)
    console.log(timer)  // 1
  • 2
  • 3
  • 4

  • */

    ### 2. 革除定时器
  • 如何革除定时器?

    只须要定时器的返回值编号革除即可。

    let count = 1
    let timer = setInterval(function(){
        count++
        console.log(count)
        // count == 3 ? clearTimeout(timer) : null
        count == 3 ? clearInterval(timer) : null
    }, 1000)

二、异步编程的原理

先来看一个小例子

let a = 0
setTimeout(() =>{console.log('a', ++a)
}, 0)
console.log(a)
/* 输入
*   0
*   1
*/

下面的例子中,setTimeout 是异步的,浏览器会将异步的代码退出到工作队列中,等到同步的代码执行实现后才执行异步的代码

1. 同步

JS 是单线程的,代码至上而下执行时遇到同步的代码须要先执行完才能够进行下一步工作。比方循环等

2. 异步

所有须要期待的工作都是异步的。遇到异步代码时,不须要期待而是间接异步工作放入工作队列,等到前面的工作实现后,才会返回来执行没有实现异步的代码。比方事件绑定,所有定时器,ajax 的异步解决,局部回调函数,浏览器的渲染过程等等。

let a = 0
setTimeout(() =>{console.log('a', ++a)
}, 0)
console.log(a)
while(true){}

下面的代码死循环了,即便定时器的工夫到了也不会执行。因为同步的代码没有执行完一步就不会执行。

三、promise

1. 基本概念

Promise 只是一个治理异步编程的类,自身是同步。Promise 有三个状态 pending/fulfilled/rejected,三个状态只有两个状态呈现要么胜利要么失败。new Promise()时必须要传入回调函数 executor,否则报错。其中回调函数中有两个参数 resolve, reject,这两个参数可不写。

  • pending:初始化状态,开始执行异步的工作,只有执行 new,new Promise(()=>{}),promise 的状态就会变成 pending
  • fulfilled:胜利状态,执行 resolve()
  • rejected:失败状态, 执行 rejected()
    先看一个小栗子。

    new Promise(()=> {setTimeout(()=> {console.log(1)
        }, 0)
        console.log(2)
    }).then()
    console.log(3)
  • 2
  • 3
  • 1
    */

    > 创立一个新的 `Promise` 的实例也就是 `new` 这个过程中会把 `Promise 中 ` 的函数先执行(不分明 `new` 创立实例的过程中产生了什么能够看看这篇 [面试 | 你不得不懂得 JS 原型和原型链](https://juejin.cn/post/6938590449674223624#heading-6))。函数体内有异步操作的仍会退出工作队列,等到同步执行实现后才执行异步工作,比方函数体内的 `setTimeout 函数 `。所以输入的后果就是 `2,3,1`。

再来看一个小栗子

const promise = new Promise((resolve, reject) => {resolve('success1')
  reject('error')
  resolve('success2')
})

promise.then((res) => {console.log('then:', res)
}).catch((err) => {console.log('catch:', err)
}
// then: success1

promise 的状态只能扭转一次,最初的状态不是 fulfilled 就是 rejected。也就是说同一个 promise 中的 resolve()/ reject() 只能执行一个且一次。

2. promise 是怎么治理异步的

promise 参数的回调函数体内接管两个参数 resolve/ reject,这两个参数能够作为两个回调函数。

  1. resolve():是异步操作执行胜利后执行,promise 的状态变成了 fulfilled,能够提供返回值,在 .then()中第一个参数接管
  2. reject():异步操作执行失败后执行,promise 的状态变成了 rejected,能够提供返回值,在 .then() 中第二个参数接管
    resolve()reject() 中只能传递一个参数。promise 的状态产生扭转后不会再变动。
    resolve() 和 reject() 是异步操作,执行这两个办法时,会先执行 resolve/reject 上面的同步代码,等到主工作为空时,再去调用 resolve/reject 把寄存的办法执行。
    Promise 对象上有公有属性Promise.resolve()/ Promise.reject() 等。
    举一个没什么意义的小栗子
new Promise((resolve, reject)=> {setTimeout(()=> {console.log('林')
        resolve('ok')
        // reject('fail')
        console.log('一一')
    }, 0)
}).then( res => {console.log('status:', res)
}, res => {console.log('status:', res)
})
// 林 
// 一一 
// ok

下面的栗子间接看出 resolve/reject 是异步的。

3. promise.then(onfulfilled, onrejected) / promise.catch()

  • promise.then(onfulfilled, onrejected)

    1. promise.then() 办法中有两个参数,别离对应着 promise 的两种不同的状态,fulfilled, rejected。对应的状态执行对应的办法。
    2. promise.then() 中的参数是函数,如果传递的不是函数就会造成 值穿透 ,也就是resolve()/reject() 的返回值会.then() 中接管。
    3. promise.then() 可能链式的调用,可能链式调用的起因不是.then() 办法中有return this,而是每一个 .then() 办法中都会返回一个新的Promise 实例。
  • promise.catch()

    1. promise.catch()promise.then() 第二个参数的简便写法,也就是用来捕捉 reject() 执行后的 rejected状态。
    2. .catch()也能够实现链式调用,起因和.then() 办法一样都是返回了一个新的 promise

.then()/.catch() 中的返回值都不能是 promise本人自身的实例,因为会造成死循环

热身题

1. 举一个小栗子

Promise.resolve(1)
    .then((res) => {console.log(res)
        return 2
    }).catch(err => {console.log(err)
    }).then( res => {console.log(res)
    })
// 1 2

最终输入 1 2,

2. 举一个值穿透的小栗子

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

// 等价于
// Promise.resolve(1)
//   .then(console.log)

3. then()/.catch() 内不能返回本人自身的 promise 实例,举一个栗子

let pro = Promise.resolve()
  .then(() => {console.log('promise', pro)
    return pro
  })
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

创立的promise 实例是pro, 返回值就不能是pro, 否则会造成死循环。

5. Promise.all([promise1, promise2…])

Promise.all() 中须要期待参数中所有 promise 的状态都胜利才执行回调的.then(),如果有一个是失败的那么就执行 .catch()。接管的参数是一个蕴含 promise 实例的数组,.all() 这个办法的返回值也是一个新的promise 实例。

  • 全副执行胜利回调 .then() 接管到的就是一个数组
  • 如果有执行失败的 promise 状态,回调 .catch 中就会捕捉到执行失败的 promisepromise.all() 执行完结。

    var p1 = Promise.resolve(1)
    var p2 = Promise.resolve(2)
    var p3 = Promise.resolve(3)
    let pro =  Promise.all([p1, p2, p3])
      .then(res => {console.log(res)  //  [1, 2, 3]
      })
      .catch( err => {console.log(err)
      })
    console.log(pro)    // Promise {<pending>}

    全副执行胜利,那么.then() 获取到的值就是resolve() 的返回值数组。

    6. Promise.race()

    .race() 的作用也是接管一组异步工作,而后并行执行异步工作,只保留取一个最快执行实现的异步操作的后果,其余的办法仍在执行,不过执行后果会被摈弃。

  • 接管的是一组数组,只获取一个最快执行实现 resolve()/rejected() 的返回值,返回值不是一个数组

    var p1 = new Promise(function(resolve, reject) {setTimeout(reject, 500, "one");
    });
    var p2 = new Promise(function(resolve, reject) {setTimeout(resolve, 100, "two");
    });

Promise.race([p1, p2]).then(function(value) {
console.log(value); // “two”
});

> 两个都实现,但 p2 更快

### 7. Promise.finally()
> `.finally` 办法也是返回一个 `Promise`,他在 `Promise` 完结的时候,无论后果为 `resolved` 还是 `rejected`,都会执行外面的回调函数。

let promise = new Promise((resolve, reject) => {

reject('error')

}).then(res => {

console.log('then', res)
return res

}).catch(err => {

console.log('catch', err)   // `catch error`
 return err

}).finally(() => {

console.log('finally')  //'finally'

})


### 热身题
#### 热身题 1 

const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
// 1, 2, 4, 3

> 因为 `resolve()` 是异步的,`promise.then` 也是异步的,在没有获取到 `resolve()` 的 `fulfilled` 状态时 `.then()` 不会执行。#### 热身题 2 

const promise = new Promise((resolve, reject) => {
setTimeout(() => {

console.log('once')
resolve('success')

}, 1000)
})

const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() – start)
})
promise.then((res) => {
console.log(res, Date.now() – start)
})
/* 输入

  • once
  • success 1002
  • success 1002
    */

    > `promise.then()` 即便有多个,然而 `resolve()/reject()` 后的调用时同时执行的。所以同时输入了 `success 1002`

三、async 和 await

先来看一个栗子

function fn(){return new Promise((resolve, reject) => {setTimeout( () => {Math.random() < 0.5 ? resolve('resolve 001') : reject('reject 002')
        }, 0)
    })
}

async function get() {let res = await fn()
    console.log(res)
    console.log(1212)
}
get()

1. async

  1. asyncawait 是 ES7 中减少来对 promise 操作的办法,是 ES7 系列提供的语法糖,await 不能独自应用肯定要联合 async 来应用。
  2. async 会返回一个 promise 对象,async 函数调用不会造成代码的阻塞

2. await

  1. await 是用来 期待获取 一个 promiseresolve/reject 的执行后果,像下面的 let res = await fn() 是先把 fn() 执行后,来获取 resolve/reject 返回的后果,不过 await 前面也能够不跟着一个 promise,然而这样写就没有意义了。
  2. await 或 await fn() 这个操作不是同步的,而是异步的。await 上面的代码不会执行,而是移入到工作队列期待区,等到主栈中的其余工作实现且 fn() 中的 promise 将后果返回,await 上面的代码才能够从新回到主栈中执行。await 能够使 promise 的操作更加像同步的代码。
  3. 如果 await 等到的获取后果是 reject() 返回的,那么 await 上面的代码,就不会再执行,因为曾经报错了。
    举一个小栗子, 阐明 async/await 是语法糖
async function async1() {console.log('async1 start');
    await async2();
    console.log('async1 end');
}
// 下面的代码等价于 ==>
async function async1() {console.log('async1 start');
    Promise.resolve(async2()).then(() => {console.log('async1 end')
    })
}

思考

热身 1,await 是同步吗?,求输入的后果

console.log(1)
function fn(){return new Promise((resolve, reject) => {console.log(5)
        resolve('resolve 001')
        console.log(6)
    })
}

async function get() {console.log(2)
    let res = await fn()
    console.log(res)
    console.log(3)
}
get()
console.log(4)
//1, 2, 5, 6, 4, resolve 001, 3

await 是异步的同时会让出线程,fn() 执行后线程开始让出,那么 await 的前面的代码不会立刻执行会先到主栈的期待区,主栈中 console.log(4) 执行后,再回来执行 await 处的代码。

2. 热身 2,求输入后果

console.log(1)
async function get() {console.log(2)
    let res = await 200
    console.log(res)
    console.log(3)
}
get()
console.log(4)
// 1, 2, 4, 200, 3

依据下面一题 await 异步代码的起因,能够容易的剖析出答案。同时阐明 await 前面也能够跟着一个非 promise 的实例。

四、经典面试题

1. promise 的优缺点 / 为什么应用 promise?

  • 长处:promise 能够解决回调天堂,promise 大大加强了嵌套函数的可读性和可维护性,
  • 毛病:无奈勾销 Promise,谬误须要通过回调函数来捕捉;如果不设置回调函数,Promise 外部抛出的谬误,不会反映到内部;当处于 pending(期待)状态时,无奈得悉目前停顿到哪一个阶段,是刚刚开始还是行将实现

2. setTimeout、Promise、Async/Await 的区别

  • setTimeout: setTimeout 的回调函数放到宏工作队列里,等到执行栈清空当前执行
  • Promise: Promise 自身是同步的立刻执行函数,当在 executor 中执行 resolve 或者 reject 的时候,此时是异步操作,会先执行 then/catch 等,当主栈实现时,才会去调用 resolve/reject 办法中寄存的办法。
  • async: async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作实现,再执行函数体内前面的语句。能够了解为,是让出了线程,跳出了 async 函数体。

3. 实现一个 sleep 函数,比方 sleep(1000) 意味着期待 1000 毫秒。

function sleep1(time) {
    return new Promise(resolve => {setTimeout(() => {resolve();
        }, time);
    })
}
sleep1(1000).then(() => console.log("sleep1"));

4. Promise 构造函数是同步执行还是异步执行,那么 then 办法呢

Promise 是同步的,执行 new promise(callback) 时回调函数callback 就会被立刻执行,then() 办法是异步的。

const promise = new Promise((resolve, reject) => {console.log(1)
    resolve()
    console.log(2)
})
promise.then(() => {console.log(3)
})
console.log(4)

1243,promise 构造函数是同步执行的,then 办法是异步执行的

5. 介绍下 Promise.all 应用、原理实现及错误处理

const p = Promise.all([p1, p2, p3]);

Promise.all 办法承受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用上面讲到的 Promise resolve 办法,将参数转为 Promise 实例,再进一步解决。(Promise.all 办法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每个成员都是 Promise 实例。)

5. 介绍下 Promise.all, Promise.race() 的区别

看下面的介绍

Promise 的各种 api 实现会在下一篇文章中实现。给个期待吧😂

五、参考

Promise 必知必会(十道题)

BAT 前端经典面试问题:史上最最最具体的手写 Promise 教程

MDN async/await

六、完结

感激浏览到这里,如果着篇文章能对你有一点启发或帮忙的话欢送 github star, 我是林一一, 下次见。

正文完
 0