Promise面试题思考延伸

简介: 最近想起之前在V2EX上看到的一个问题:一个 async function 数组, 怎样一个一个顺序执行?[1]

最近想起之前在V2EX上看到的一个问题:一个 async function 数组, 怎样一个一个顺序执行?[1]


想做到的是,每过一秒,分别打印:this is 0this is 1this is 2~this is 8


下面的代码结果是过一秒后全部执行。


是不是哪里写的不对呢,多谢指教


var jobs = [];
for(let i = 0; i < 8; i++) {
 jobs.push(async function(){
     setTimeout(function(){
         console.log("this is " + i)
     },1000)
 });
}
(async function () {
 for (const job of jobs) {
     await job()
 }
})();


这题考察对 Promise 、async/await 的理解。而这题又能让人联想到一个类似的setTimeout 循环问题——破解前端面试(80% 应聘者不及格系列):从 闭包说起[2]


请问以下代码打印出什么数据


for (var i = 0; i < 5; i++) {
 setTimeout(function() {
     console.log(new Date, i);
 }, 1000);
}
console.log(new Date, i);


如何修改成每隔1秒打印一个数,以0、1、2、3、4、5 的顺序排列,并要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?


此题面试官想考察循环、闭包、闭包的解决(IIFE)、ES6 知识点(let、Promise)、ES7 的Async/await,以及setTimeout  的第三个参数等等知识点,具体看文章能明白一二


这里我们讲第一道题目,如果要实现题主所说的效果,缺少了什么?


已知:let形成块级作用域,意味着每次循环,jobs就 push 一个 async 函数,这些都是同步执行


但是注意,async 中的函数也是同步执行,只有等到 await 时才会进入微任务中,所以当


  • i=0时,jobs 塞入一个 setTimeout(function() { console.log("this is 0" )})
  • i=1时,jobs 塞入一个 setTimeout(function() { console.log("this is 1" )})
  • i=2时,jobs 塞入一个 setTimeout(function() { console.log("this is 2" )})
  • i=3时,jobs 塞入一个 setTimeout(function() { console.log("this is 3" )})
  • ...
  • i=7时,jobs 塞入一个 setTimeout(function() { console.log("this is 7" )})


继续往下执行


(async function () {
    for (const job of jobs) {
        await job()
    }
})();


这里题主了解到 await 需要 async 配合使用,就写了立即执行匿名函数,执行数组 jobs,但问题是 jobs 中的每个子项都是执行 async function(){setTimeout},这里的 async 有意义吗?


jobs.push(async function(){
    setTimeout(function(){
        console.log("this is " + i)
    },1000)
});


如果要让 await 暂停进程并恢复进程(即await job()),我们需要的是什么?


去掉 async,使其变成一个普通的函数,结果执行结果一致


jobs.push(function () {
    setTimeout(function () {
        console.log("this is " + i)
    }, 1000)
});


同样,将普通函数改成箭头函数也是如此,一秒之后打印还是0~7


根据网友[3]总结:


  1. 对于 promise 对象,await 会阻塞函数执行,等待 promise 的 resolve 返回值,作为 await 的结果,然后再执行下一个表达式


  1. 对于非 promise 对象,比如 箭头函数、同步表达式等等,await 等待函数或者直接量的返回,而不是等待其执行结果

所以如果要让 await 每隔1秒执行一个 job,那就需返回一个 promise 实例,基于此逻辑进行改造


...
jobs.push(function () {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve()
         console.log("this is " + i)
     }, 1000)  
    })
});
...


这样,就解决了这个问题


我们的逻辑是在循环中,每次向 jobs 中塞入一个函数,这个函数返回的是一个实例 Promise(即 await 遇到后会暂停等异步结束再继续后续代码)。当执行 await job() 时,我们知道是循环 jobs,await 让其等待执行,执行完第一个后,再执行第二个,循序执行,每一个等待1秒钟,就达到题目的要求


这里我们了解到 await 等待的是一个 promise 实例(如果非 promise 实例,就不用等待),既然说到 Promise,我们就延伸一下,then 的链式调用


Promise 的 then 方法支持链式调用,它有哪几种情况?


  • 不 return(返回)值, 值延续上一个 resolved


  • return


  • return 非 Promise 实例


  • return Promise 实例


不 return



const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('johan')
  }, 2000)
})
promise.then((data) => {
    console.log(data)
    // 不返回任何值
}).then((data) => {
    console.log('第二次', data)
})


答案:johan、第二次 undefined


return 非 Promise 实例



const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('johan')
  }, 2000)
})
promise.then((data) => {
    console.log(data)
    return '帝王johan'
}).then((data) => {
    console.log('第二次', data)
})


答案:johan、第二次 帝王johan


return Promise 实例



const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('johan')
  }, 2000)
})
promise.then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${data} next`)
        }, 4000)
    })
}).then((data) => {
    console.log('第二次', data)
})


答案:johan、第二次 johan next


以上三个例子可以得知,在 then 方法中的  onfulfilled 函数和 onrejected 函数,不仅支持不返回,而且支持非 Promise 实例的普通值,而且支持一个 Promise 实例。并且返回的这个 Promise 实例或非 Promise 实例的普通值将会传给下一个 then 方法的 onfulfilled 函数或者 onrejected 函数中


因为我们知道它是 Generator 函数的语法糖[4]async 函数返回的是一个 Promise 对象,当函数执行时,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句


我们来一道题来测试一下


const myPromise = val => Promise.resolve(val);
const delay = duration => { /**/ };
myPromise(`hello`).then(delay(1000)).then(val => console.log(val)); // hello


myPromise 是个 Promise 实例,传入值 hello,经过 Promise.resolve 传到then 中,然后经 delay 再传递给下一个 then,打印出val,所以 delay(1000) 会返回一个 Promise 实例,这样,第二个 then 才能打印出 hello


const delay = duration => (val) => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(val)
    }, duration)
})


参考资料



[1] 一个 async function 数组, 怎样一个一个顺序执行?: https://www.v2ex.com/t/810025


[2] 破解前端面试(80% 应聘者不及格系列):从 闭包说起: https://zhuanlan.zhihu.com/p/25855075


[3] 网友: https://blog.csdn.net/qq_38990451/article/details/114869256


[4] Generator 函数的语法糖: https://es6.ruanyifeng.com/?search=async&x=0&y=0#docs/async


相关文章
|
6月前
|
前端开发 JavaScript API
【面试题】说说 Promise是什么?如何使用
【面试题】说说 Promise是什么?如何使用
|
6月前
|
前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
|
6月前
|
存储 运维 前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
|
1月前
|
存储 前端开发 JavaScript
关于 ES6 中 Promise 的面试题
关于 ES6 中 Promise 的面试题
16 0
|
6月前
|
前端开发 JavaScript
No101.精选前端面试题,享受每天的挑战和学习(Promise)
No101.精选前端面试题,享受每天的挑战和学习(Promise)
|
6月前
|
前端开发 JavaScript API
【面试题】面试官:为什么Promise中的错误不能被try/catch?
【面试题】面试官:为什么Promise中的错误不能被try/catch?
|
前端开发 JavaScript
Promise入门/面试必看
Promise入门/面试必看
69 0
|
设计模式 JSON 前端开发
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
73 0
|
前端开发 JavaScript
web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码
web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码
174 0
|
前端开发 算法 安全
前端面试100道手写题(1)—— 手写Promise实现
今年的金三银四面试,遇到了很多新的面试八股文,其实心里对手写题或者算法题有一定抵触,因为实际工作中基本上就不会用到这些东西,但是正因为这些基础八股文,才能真正验证一个人对技术有多热爱的程度。也有可能近几年没有对这些基础知识进行巩固,所以干脆一狠心,先立个flag, 准备完成100道手写题。
257 0

热门文章

最新文章