天天看点

前端面试题:Promise

作者:科技树乱点
前端面试题:Promise

#挑战30天在头条写日记#

Promise.resolve()
  .then(() => {
    console.log(0)
    return Promise.resolve(4)
  })
  .then(res => {
    console.log(res)
  });

Promise.resolve()
  .then(() => {
    console.log(1)
  })
  .then(() => {
    console.log(2)
  })
  .then(() => {
    console.log(3)
  })
  .then(() => {
    console.log(5)
  })
  .then(() => {
    console.log(6)
  })           

上面代码的输出结果是什么?为什么会输出这样的结果呢?

核心知识点

JavaScript中的任务和微任务:

前端面试题:Promise

在JavaScript中,任务(Task)是指要在JavaScript引擎中执行的一个工作单元。任务可以是同步的(立即执行)或异步的(延迟执行或在后台执行)。任务被放入任务队列中,等待JavaScript引擎执行。

微任务(Microtask)和宏任务(Macrotask)是两种不同类型的任务,它们被添加到不同的任务队列中,且执行顺序也有所不同。

微任务(Microtask):

微任务是一类需要尽快执行的任务,它们执行在当前任务(当前执行栈)结束之后、下一个任务(事件循环迭代)开始之前。也就是说,当当前执行栈中的代码执行完毕,JavaScript引擎会立即检查微任务队列,并在执行栈为空时按顺序执行微任务队列中的任务。

常见产生微任务的方式是使用Promise、async/await、queueMicrotask(标准化的微任务API)等。

例如:

console.log('Start');

Promise.resolve().then(() => console.log('Microtask 1'));
Promise.resolve().then(() => console.log('Microtask 2'));

console.log('End');           

输出结果:

Start
End
Microtask 1
Microtask 2           

在javascript中创建微任务有哪些方式?

Promise:

Promise 是一种内置对象,它表示一个异步操作的最终完成或失败,并返回一个结果值。Promise的回调函数(.then()、.catch()、.finally())会被放入微任务队列,等待当前执行栈为空时执行。

const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 异步操作成功 */) {
    resolve(result); // 将结果传递给.then()回调
  } else {
    reject(error); // 将错误传递给.catch()回调
  }
});

myPromise.then(result => {
  // 处理成功结果
}).catch(error => {
  // 处理错误情况
}).finally(() => {
  // 最终处理,不管成功还是失败都会执行
});           

async/await:

async/await 是 Promise 的语法糖,它使得异步代码看起来更像同步代码。async 函数内部使用 await 来等待 Promise 完成,它们也会创建微任务。

async function myAsyncFunction() {
  try {
    const result = await someAsyncOperation(); // 等待异步操作完成
    // 处理成功结果
  } catch (error) {
    // 处理错误情况
  } finally {
    // 最终处理
  }
}           

MutationObserver:

MutationObserver 是一个用于监测 DOM 变化的接口,它可以用于监听 DOM 的变化并在变化发生后执行回调函数,该回调函数会被放入微任务队列。

const targetNode = document.getElementById('target');

const observer = new MutationObserver(mutations => {
  // 处理 DOM 变化
});

const config = { attributes: true, childList: true, subtree: true };

observer.observe(targetNode, config);           

宏任务

宏任务是一类需要在当前任务之后、下一个事件循环迭代开始之前执行的任务。它们在任务队列中排队,只有当执行栈为空(所有微任务执行完毕)时,才会从宏任务队列中取出一个任务执行。宏任务可以通过setTimeout、setInterval、requestAnimationFrame、DOM事件回调(例如点击事件、AJAX回调)等来创建,这些任务会被放入宏任务队列。请注意,在每次事件循环中,微任务优先于宏任务执行。

console.log('Start');

setTimeout(() => console.log('Macrotask 1'), 0);
setTimeout(() => console.log('Macrotask 2'), 0);

console.log('End');           

输出结果:

Start
End
Macrotask 1
Macrotask 2           

任务队列按照先进先出(FIFO)的顺序执行任务,即先进入队列的任务先执行。在事件循环的每一次迭代中,JavaScript引擎会依次执行当前的微任务队列中的所有任务,然后执行当前的宏任务队列中的一个任务,然后进入下一次事件循环迭代。这样的过程不断重复,形成了事件循环的机制。

面试题分析

前端面试题:Promise
前端面试题:Promise
前端面试题:Promise
前端面试题:Promise
前端面试题:Promise
前端面试题:Promise

上面的图示里,可以看到整个宏任务执行的过程,Promise.then创建了微任务队列。到这一步,宏任务执行完毕,微任务队列也构建完毕,接下来开始执行微任务队列里的内容。此时微任务队列里的任务如下所示:

Promise.resolve()
  .then(() => {  // microtask 1
    console.log(0) // 打印0
    // 1.返回一个Promise对象
    // 2.resolve 4,返回4,进入then
    return Promise.resolve(4)
  })
  .then(res => { // microtask 1-1
    console.log(res)
  });

Promise.resolve()
  .then(() => { // microtask 2
    console.log(1) // 打印1,这时microtask1的1执行完成
  })
  .then(() => { // microtask 3
    console.log(2) // 打印2,这时microtask1的2执行完成,resolve了4并进入then
  })
  .then(() => { // microtask 4
    console.log(3) // 打印3,这时microtask 1-1执行,打印4
  })
  .then(() => { // microtask 5
    console.log(5) // 继续执行,打印5
  })
  .then(() => { // microtask 6
    console.log(6) // 继续执行,打印6
  })           

延伸扩展

setTimeout(() => {
  console.log(`sto 0`)
}, 0);
Promise.resolve()
  .then(() => {
    setTimeout(() => {
      console.log(`sto microtask1`)
    }, 0);
    console.log(0)
    return Promise.resolve(4)
  })
  .then(res => {
    setTimeout(() => {
      console.log(`sto microtask1-1`)
    }, 0);
    console.log(res)
  });

setTimeout(() => {
  console.log(`sto 1`)
}, 0);
Promise.resolve()
  .then(() => {
    setTimeout(() => {
      console.log(`sto microtask2`)
    }, 0);
    console.log(1)
  })
  .then(() => {
    console.log(2)
  })
  .then(() => {
    console.log(3)
  })
  .then(() => {
    console.log(5)
  })
  .then(() => {
    console.log(6)
  });
setTimeout(() => {
  console.log(`sto 2`)
}, 0);           

上面的代码会怎么样输出呢?为什么呢?