JavaScript原理篇——Promise原理及笔试题实战演练

news2024/11/28 4:26:57

Promise 是 JavaScript 中用于处理异步操作的对象,它代表了一个可能还没有完成的操作的最终完成或失败,以及其结果值。Promise 对象有三种状态:

  1. Pending(进行中):初始状态,既不是成功,也不是失败状态。
  2. Fulfilled(已成功):操作成功完成。
  3. Rejected(已失败):操作失败。

一个 Promise 对象一旦从 Pending 状态转变为 Fulfilled 或 Rejected,它的状态就不会再改变。

Promise 的核心原理包括:

  • 构造函数:通过 new Promise(executor) 创建 Promise 对象,其中 executor 是一个带有两个参数的函数,通常称为 resolvereject,它们分别用于将 Promise 状态改为 Fulfilled 和 Rejected。
  • 状态管理:Promise 对象的状态只能从 Pending 变为 Fulfilled 或从 Pending 变为 Rejected,一旦状态改变,就不能再变。
  • 链式调用:通过 .then().catch() 方法可以链式调用 Promise,处理异步操作的结果或错误。
  • 异步执行:Promise 的 executor 函数是立即执行的,但其 resolvereject 函数是异步调用的。
  • 错误处理:通过 .catch() 方法或 .then(null, onRejected) 可以捕获链式调用中的错误。

你真的完全了解promise吗,请输出以下代码题的结果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

 答案:

 如果你回答的和答案不一致,请务必接收下面的Promise解题指南

 Promise必备能力

Promise 是 JavaScript 中处理异步操作的一种机制,它代表了一个异步操作的最终完成或失败,并可以获取其结果。以下是 Promise 的关键知识点,掌握这些内容将有助于应对 Promise 相关的笔试题:

1. Promise 的基本概念

  • 状态(State):一个 Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
  • 结果(Result):Promise 的状态一旦从 pending 变为 fulfilledrejected,就不再改变,且会有一个不可变的终值或拒因。

2. Promise 的创建

  • 使用 new Promise(executor) 构造函数创建 Promise,其中 executor 是一个带有两个参数的函数:resolvereject,分别用于将 Promise 的状态变为 fulfilledrejected

3. Promise 的方法

  • then(onFulfilled, onRejected):注册成功和失败时的回调函数,返回一个新的 Promise。
  • catch(onRejected):注册失败时的回调函数,相当于 .then(null, onRejected),返回一个新的 Promise。
  • finally(onFinally):注册无论成功或失败都会执行的回调函数,返回一个新的 Promise。

4. Promise 链

  • .then.catch 方法返回的都是新的 Promise 实例,这允许链式调用,形成 Promise 链。
  • 链中的每个 .then.catch 都会返回一个新的 Promise,其状态和结果取决于前一个 Promise 的状态和回调函数的返回值。

5. Promise 的静态方法

  • Promise.resolve(value):返回一个以给定值解析的 Promise 对象。
  • Promise.reject(reason):返回一个带有拒绝原因的 Promise 对象。
  • Promise.all(iterable):返回一个 Promise,当 iterable 中所有 Promise 都 fulfilled 时,返回的 Promise 才会 fulfilled,其结果是一个包含所有 Promise 结果的数组。如果有一个 Promise 被 rejected,则返回的 Promise 立即 rejected,且结果为第一个被 rejected 的 Promise 的拒因。
  • Promise.race(iterable):返回一个 Promise,一旦 iterable 中的某个 Promise fulfilled 或 rejected,返回的 Promise 就会采用相同的状态和结果。

6. Promise 的错误处理

  • 未处理的 Promise 拒绝会被 unhandledrejection 事件捕获。
  • 使用 .catch.then(null, onRejected) 可以捕获 Promise 链中的错误。

 Promise 的性能考虑:Promise 的创建和执行是异步的,不会阻塞主线程。避免不必要的 Promise 创建,因为每个 Promise 都会占用一定的内存。

 Promise 的局限性:Promise 一旦状态改变,就无法再次改变。Promise 没有提供取消异步操作的机制。

事件循环机制

事件循环(Event Loop)是 JavaScript 运行时环境中的一个重要概念,它与 Promise 的使用密切相关,因为 Promise 的回调是通过事件循环来执行的。理解事件循环机制有助于解决与异步编程、回调函数、Promise 和 async/await 等相关的问题。作为前端开发者,你必须要理解并掌握下面的内容:

  • 理解事件循环的基本流程和宏任务、微任务的区别。
  • 知道 Promise 的回调是如何通过事件循环执行的,以及为什么 .then.catch 的回调通常比 setTimeout 更快执行。
  • 应用到实际问题中,比如理解为什么在 setTimeoutPromise 的回调中,await 前的代码会立即执行,而 await 后的代码则在 Promise 解决后执行。
  • 理解事件循环与异步编程(如 async/await)的关系,以及如何避免回调地狱(Callback Hell)。

1. 事件循环的工作原理

  • 宏任务(Macrotask):主要包括 setTimeoutsetIntervalI/O 操作UI 渲染(如浏览器的重绘和重排)

  • 微任务(Microtask):主要包括 Promise 的 .thenprocess.nextTick 中的回调。

  • 执行栈(Execution Context Stack):用于执行同步代码,每次执行栈空时,事件循环会从宏任务队列中取出一个宏任务执行,然后执行所有微任务,再继续取下一个宏任务。

  • 事件循环的流程

    1. 执行同步代码直到执行栈为空。
    2. 从宏任务队列中取出一个宏任务执行,执行完毕后执行所有微任务。
    3. 重复步骤 2 直到宏任务队列和微任务队列都空。
    4. 如果有新的微任务(如 process.nextTick),将其添加到微任务队列。

2. 与 Promise 的关系

  • 当一个 Promise 被 resolve 或 reject 时,它会创建一个微任务,这个微任务会将回调函数添加到微任务队列中,等待执行。
  • .then.catch 的回调会在微任务队列中执行,这意味着它们会等待当前的宏任务和微任务执行完毕后才会运行。
  • 如果在回调中又创建了新的 Promise,会形成一个微任务链,直到微任务队列为空

注意:宏任务是一次执行一个,微任务是一次清空一个队列。两者有本质区别

Promise的几道基础题

1. 题目一

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
})
console.log('1', promise1);

思路:Promise本身是同步的。从上至下,先遇到new Promise,执行该构造函数中的代码promise1。然后执行同步代码1,此时promise1没有被resolve或者reject,因此状态还是pending

答案:

2 题目二

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

思路:从上至下,先遇到new Promise,执行其中的同步代码1

再遇到resolve('success'), 将promise的状态改为了resolved并且将值保存下来

继续执行同步代码2

跳出promise,往下执行,碰到promise.then这个微任务,将其加入微任务队列

执行同步代码4

本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。

答案:

3 题目三

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

 思路:和题目二相似,只不过在promise中并没有resolve或者reject。因此promise.then并不会执行,它只有在被改变了状态之后才会执行

答案:

4 题目四

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

思路:从上至下,先遇到new Promise,执行该构造函数中的代码promise1

碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来

碰到promise1.then这个微任务,将它放入微任务队列

promise2是一个新的状态为pending的Promise`

执行同步代码1, 同时打印出promise1的状态是resolved

执行同步代码2,同时打印出promise2的状态是pending

宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。

5 题目五

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
}))
fn().then(res => {
  console.log(res)
})
console.log('start')

 思路:fn函数它是直接返回了一个new Promise的,而且fn函数的调用是在start之前,所以它里面的内容应该会先执行。

答案:

6 题目六

如果把fn的调用放到start之后呢?

const fn = () =>
  new Promise((resolve, reject) => {
    console.log(1);
    resolve("success");
  });
console.log("start");
fn().then(res => {
  console.log(res);
});

思路:fn是一个函数,只有在函数调用的时候才会执行。因此先执行console.log("start"),再处理fn,之后是then方法

答案:

Promise结合setTimeout

Promise通常会结合事件循环机制进行考察异步输出结果

1 题目一 

console.log('start')
setTimeout(() => {
  console.log('time')
})
Promise.resolve().then(() => {
  console.log('resolve')
})
console.log('end')

思路:刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈进行执行,因此先打印出start和end。

setTimout作为一个宏任务被放入宏任务队列(下一个)

Promise.then作为一个微任务被放入微任务队列

本次宏任务执行完,检查微任务,发现Promise.then,执行它

接下来进入下一个宏任务,发现setTimeout,执行。

答案:

2. 题目二

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

思路:从上至下,先遇到new Promise,执行该构造函数中的代码1

然后碰到了定时器,将这个定时器中的函数放到下一个宏任务的延迟队列中等待执行 执行同步代码2

跳出promise函数,遇到promise.then,但其状态还是为pending,这里理解为先不执行 执行同步代码4

一轮循环过后,进入第二次宏任务,发现延迟队列中有setTimeout定时器,执行它

首先执行timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列

继续执行同步代码timerEnd

宏任务全部执行完毕,查找微任务队列,发现promise.then这个微任务,执行它

答案:

3 题目三

题目三分了两个题目,因为看着都差不多,不过执行的结果却不一样,大家不妨先猜猜下面两个题目分别执行什么:

setTimeout(() => {
  console.log('timer1');
  setTimeout(() => {
    console.log('timer3')
  }, 0)
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')

思路:分析代码,还是先执行同步代码;然后将setTimeout放入宏任务队列中。对于第一个题,同步执行完,宏任务队列执行,输出timer1,此时又遇到一个setTimeout,继续加入队列,并从队头取出timer2并输出。

对于第二个代码:先执行同步代码,输出start;然后执行setTimeout,输出timer1,遇到微任务promise.resolve.then,此时promise的状态也是resolve,因此可以执行then。并且then是微任务,timer1之后后执行微任务,输出promise。

答案:

第一个输出结果

 
 

第二个输出结果

 4.题目四

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

思路:刚开始整个脚本作为第一次宏任务来执行,我们将它标记为宏1,从上至下执行。

遇到Promise.resolve().then这个微任务,将then中的内容加入第一次的微任务队列标记为微1

遇到定时器timer1,将它加入下一次宏任务的延迟列表,标记为宏2,等待执行(先不管里面是什么内容)

执行宏1中的同步代码start

第一次宏任务(宏1)执行完毕,检查第一次的微任务队列(微1),发现有一个promise.then这个微任务需要执行

执行打印出微1中同步代码promise1,然后发现定时器timer2,将它加入宏2的后面,标记为宏3

第一次微任务队列(微1)执行完毕,执行第二次宏任务(宏2),首先执行同步代码timer1

然后遇到了promise2这个微任务,将它加入此次循环的微任务队列,标记为微2

宏2中没有同步代码可执行了,查找本次循环的微任务队列(微2),发现了promise2,执行它

第二轮执行完毕,执行宏3,打印出timer2

 

5.题目五

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

思路:从上至下,先执行第一个new Promise中的函数,碰到setTimeout将它加入下一个宏任务列表。跳出new Promise,碰到promise1.then这个微任务,但其状态还是为pending,这里理解为先不执行。

  • promise2是一个新的状态为pending的Promise
  • 执行同步代码console.log('promise1'),且打印出的promise1的状态为pending
  • 执行同步代码console.log('promise2'),且打印出的promise2的状态为pending
  • 碰到第二个定时器,将其放入下一个宏任务列表
  • 第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
  • 先执行第一个定时器里的内容,将promise1的状态改为resolved且保存结果并将之前的promise1.then推入微任务队列
  • 该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是promise1.then,它抛出了一个错误,且将promise2的状态设置为了rejected
  • 第一个定时器执行完毕,开始执行第二个定时器中的内容
  • 打印出'promise1',且此时promise1的状态为resolved
  • 打印出'promise2',且此时promise2的状态为rejected
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}

6.题目六

如果你上面这道题搞懂了之后,我们就可以来做做这道了,你应该能很快就给出答案:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
    console.log("timer1");
  }, 1000);
  console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
  throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
  console.log("timer2");
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 2000);

思路:promise的then方法一定要在promise本身执行并且resolve的时候才会执行

答案:

'promise1里的内容'
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
'timer1'
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'timer2'
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}

Promise中的then、catch、finally

  • Promise 的状态一经改变(resolve 或 reject),就不能再改变。
  • .then.catch 返回一个新的 Promise。
  • .catch 能捕获上层的错误,无论被连接到哪里。
  • 在 Promise 中,返回任意一个非 Promise 的值都会被包裹成 Promise 对象。
  • 如果 Promise 内部的状态一经改变,并且有了一个值,那么后续每次调用 .then.catch 都会直接拿到该值。
  • .then.catch 中返回一个 error 对象并不会抛出错误,不会被后续的 .catch 捕获。
  • .then.catch 返回的值不能是 Promise 本身,否则会造成死循环。
  • .then.catch 的参数期望是函数,传入非函数会发生值穿透。
  • .then 方法可以接收两个参数,第一个是处理成功的函数,第二个是处理失败的函数。
  • .catch 可以认为是 .then 第二个参数的简便写法。
  • .finally 方法也是返回一个 Promise,在 Promise 结束时(resolved 或 rejected)都会执行里面的回调函数。

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);
  })

思路:考察promise的状态之后被更改一次

答案:

"then: success1"

2 题目二

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
  .then((res) => {
    console.log("then1: ", res);
  })
  .then((res) => {
    console.log("then2: ", res);
  })
  .catch((err) => {
    console.log("catch: ", err);
  })
  .then((res) => {
    console.log("then3: ", res);
  });

 思路:先被reject改变状态。因此promise不会走then方法,而是被catch捕获。catch不管放在哪个位置都能捕获到error。同时catch默认返回一个新的promise,通过then方法输出then3

答案:

3 题目三

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

 思路:Promise通过.resolve手动更改Promise状态,因此执行then方法。return 2会被包装成resolve(2)

4.题目四

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    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)
})

  思路:Promise被执行一次,但Promise的then和catch可以被执行多次

答案:

5 题目五

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

 思路:通过new Error不会抛出错误,而是一个Promise包装的对象。这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))

  • 你可能想到的是进入.catch然后被捕获了错误。
  • 结果并不是这样的,它走的是.then里面:

答案:

6. 题目六

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

思路:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了穿透,将resolve(1) 的值直接传到最后一个then里。

7.题目七

下面来介绍一下.then函数中的两个参数。

第一个参数是用来处理Promise成功的函数,第二个则是处理失败的函数。 也就是说Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数。

让我们来看看这个例子

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
思路:then方法的第二个参数接收一个回调,如果Promise失败,执行then的第二个参数。这里的执行结果是:
'error' 'error!!!'

而如果把第二个参数去掉,就进入了catch()中:

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }).catch(err => {
    console.log('catch', err)
  })

执行结果:

'catch' 'error!!!'

但是有一个问题,如果是这个案例呢?

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })

思路:如果是then里面的方法抛出错误会被外面的catch住,而不会走then的第二个参数。由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。

因此执行结果为:

fail2 Error: error!!!
			at success

8.题目八

接着来看看.finally(),这个功能一般不太用在面试中,不过如果碰到了你也应该知道该如何处理。

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
思路:finally方法不管promise是什么状态都会执行
'promise1'
'1'
'error'
'finally1'
'finally2'

 Promise中的all和race

  • 在做下面的题目之前,让我们先来了解一下Promise.all()和Promise.race()的用法。
  • 通俗来说,.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
  • .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

1. 题目一

function runAsync(x) {
  const p = new Promise((resolve) =>
    setTimeout(() => {
      console.log(x);
      resolve(x);
    }, 1000)
  );
  return p;
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))

 思路:并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

  • all()后面的.then()里的回调函数接收的就是所有异步操作的结果。
  • 而且这个结果中数组的顺序和Promise.all()接收到的数组顺序一致

2 题目二

新增一个runReject函数,它用来在1000 * x秒后reject一个错误。

function runAsync(x) {
  const p = new Promise((resolve) =>
    setTimeout(() => {
      console.log("runAsync", x);
      resolve(x);
    }, 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((resolve, reject) =>
    setTimeout(() => {
      console.log("runReject", x);
      reject(`Error: ${x}`);
    }, 1000 * x)
  );
  return p;
}

Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then((res) => console.log(".all成功结果", res))
  .catch((err) => console.log(".all失败结果", err));

 思路:Promise.all 接收一个 Promise 数组,这里包含两个 runAsync 和两个 runReject 的调用。Promise.all 会等待所有 Promise 全部成功解决后返回一个结果数组,如果其中任何一个 Promise 拒绝,它会立即返回拒绝的 Promise 的结果。

具体执行过程如下:

  1. runAsync(1)runAsync(3) 同时启动,因为它们的 setTimeout 都设置为1秒,所以它们会在1秒后打印 "runAsync 1" 和 "runAsync 3",然后各自解决并返回 13
  2. runReject(4)runReject(2) 同时启动,它们的 setTimeout 根据传入的参数设置为4秒和2秒。runReject(4) 在4秒后拒绝,打印 "runReject 4" 和 "Error: 4",返回一个拒绝的 Promise。runReject(2) 在2秒后拒绝,打印 "runReject 2" 和 "Error: 2",同样返回一个拒绝的 Promise。.catch是会捕获最先的那个异常,在这道题目中最先的异常就是runReject(2)的结果

答案:

3 题目三

将上一题的Promise.all改成Promise.race

function runAsync(x) {
  const p = new Promise((resolve) =>
    setTimeout(() => {
      console.log("runAsync", x);
      resolve(x);
    }, 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((resolve, reject) =>
    setTimeout(() => {
      console.log("runReject", x);
      reject(`Error: ${x}`);
    }, 1000 * x)
  );
  return p;
}

Promise.race([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then((res) => console.log(".all成功结果", res))
  .catch((err) => console.log(".all失败结果", err));

思路:使用.race()方法,它只会获取最先执行完成的那个结果,其它的异步任务虽然也会继续进行下去,不过race已经不管那些任务的结果了 。Promise.race的then方法只会返回第一个成功的Promise返回结果。

 如果第一个成功的是reject状态呢

Promise.race([
  runReject(0),
  runAsync(1),
  runReject(4),
  runAsync(3),
  runReject(2),
])
  .then((res) => console.log(".all成功结果", res))
  .catch((err) => console.log(".all失败结果", err));

那么将返回失败的结果,也就是Promise.race后的catch方法

async/await的几道题

既然谈到了Promise,那就肯定得再说说async/await,在很多时候async和Promise的解法差不多,又有些不一样。不信你来看看题目一。

1. 题目一

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

思路:

  • 首先一进来是创建了两个函数的,我们先不看函数的创建位置,而是看它的调用位置 发现async1函数被调用了,然后去看看调用的内容
  • 执行函数中的同步代码async1 start,之后碰到了await,它会阻塞async1后面代码的执行,因此会先去执行async2中的同步代码async2,然后跳出async1
  • 跳出async1函数后,执行同步代码start
  • 在一轮宏任务全部执行完之后,再来执行刚刚await后面的内容async1 end。

答案:

 

2. 题目二

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  setTimeout(() => {
    console.log('timer')
  }, 0)
  console.log("async2");
}
async1();
console.log("start")

思路:

这段代码中,首先执行 async1() 函数。在 async1() 函数中,遇到 await async2() 时会暂停 async1() 函数的执行,等待 async2() 函数执行完毕。然而,在 async2() 函数中,虽然有一个 setTimeout,但是它是一个宏任务,会被放入事件队列中等待执行,而不会阻塞当前的微任务执行。

因此,执行顺序如下:

  1. 执行 async1() 函数,打印出 "async1 start"。
  2. 遇到 await async2(),暂停 async1() 函数的执行,转而执行 async2() 函数。
  3. 在 async2() 函数中,首先打印出 "async2",然后设置了一个 0 秒后执行的 setTimeout,但是这个 setTimeout 是一个宏任务,会被放入事件队列中等待执行。
  4. 继续执行主程序,打印出 "start"。
  5. 主程序执行完毕后,开始执行微任务队列中的任务,继续执行 async1() 函数中的剩余部分,打印出 "async1 end"。
  6. 最后,事件队列中的宏任务执行,执行 setTimeout 中的回调函数,打印出 "timer"。

3.题目三

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")

其实如果你能做到这里了,说明你前面的那些知识点也都掌握了,我就不需要太过详细的步骤分析了。注意:await后面是微任务,打印start后,先去async1里将await后面的微任务执行完

4.题目四

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

思路:在async1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,因此相当于一直在await,await,await却始终没有响应...所以在await之后的内容是不会执行的,也包括async1后面的 .then。

5.题目五

在上题基础上给Promise加上resolve

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise1 resolve')
  }).then(res => console.log(res))
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1670870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端js面试题--从字符串中删除删除注释代码

问题&#xff1a;从字符串中删除删除注释代码 描述&#xff1a; solution(weex,rex # and react\nflutter\nnative ssss !hybrid app, [#, !]) 写一个solution函数清除后面参数数组里面的字符串 打印效果 代码1 思路&#xff1a; 将字符全凡是有去掉标志符号的全部添加\n…

flutter 使用Scrollbar 时出现 滚动条不置顶问题

Flutter 使用 CupertinoScrollbar 、Scrollbar 与 ListView.builder 结合使用时&#xff0c; 当把 ListView.builder 边距设置为 padding: const EdgeInsets.all(0) 的时候&#xff0c; Scrollbar 的滚动条不置顶。 如图&#xff1a;右侧边上的滚动条 解决方法&#xff1a; …

什么是.faust勒索病毒?应该如何防御?

faust勒索病毒详细介绍 faust勒索病毒是一种新型的勒索软件&#xff0c;最早出现在2018年。该病毒通过加密计算机系统中的文件并要求支付赎金来解锁文件&#xff0c;从而获取经济利益。与传统的勒索软件相比&#xff0c;faust勒索病毒采用了更加先进的加密算法和隐藏技术&#…

浅析视频汇聚EasyCVR视频融合云平台在机场安防智能检测建设中的应用

一、背景 机场作为国家交通枢纽和对外开放的窗口&#xff0c;其安全运行直接关系到乘客的生命安全、国家形象以及社会经济稳定。随着全球航空业的快速发展和人们出行需求的持续增长&#xff0c;机场作为重要的交通枢纽&#xff0c;其客流量和货运量均呈现出快速增长的态势。然而…

【鸿蒙+全国产瑞芯微】智慧楼宇解决方案 | 如何实现多场景下智慧化、精细化楼宇管理?

随着数字化、智能化与工作生活的联结日渐紧密&#xff0c;聚焦人性化服务&#xff0c;以数字和科技匹配多重需求&#xff0c;加速商业楼宇智能化转型的脚步&#xff0c;逐步形成智慧楼宇产品矩阵。 方案亮点 01/数字标牌——形象展示 企业文化宣传、公告通知等 播放内容统一远…

实时追踪维修进度,报修管理小程序让你省心又省力!

随着生活、工作节奏的日益加快&#xff0c;日常的售后报修、故障报修处理流程给我们带来种种困扰。我们都知道大多数企业、个人用户还在使用传统报修方式&#xff0c;如电话报修、纸质报修单等方式&#xff0c;不仅效率低下&#xff0c;而且难以追踪维修进度&#xff0c;给我们…

C# 使用Queue高效检索树行数据符合条件的数据,并返回完整树形数据示例

最近有项目需要加载大型树数据&#xff0c;数据大概3W条 后端使用C# NET6 前端使用Vue3 elementuiplus 虚拟tree 》解决大型树数据加载 遇到的问题是后端在检索数据时&#xff0c;要返回匹配数据的完整树目录 1.因为单条数据没有存放完整路径&#xff0c;需要通过父级ID逐…

第六节笔记及作业----Lagent AgentLego 智能体应用搭建

关于 Agent 的相关理论 大语言模型存在一些局限性&#xff0c;比如会出现幻觉问题、有时效性问题以及可靠性问题。智能体的定义是具备感知、决策和行动能力的实体。智能体主要由感知部分、大脑部分和动作部分组成。智能体有多种类型&#xff0c;如 ReAct 类型&#xff08;侧重…

事务性邮件邮箱API发送邮件的要点有哪些?

事务性邮件邮箱API发送邮件怎么使用&#xff1f;API的使用方式&#xff1f; 使用邮箱API进行事务性邮件的发送&#xff0c;不仅能确保信息的准确传达&#xff0c;还能大大提高工作效率。那么&#xff0c;在使用事务性邮件邮箱API发送邮件时&#xff0c;我们应该注意哪些要点呢…

triton之gemm

一 原理 如果不采用group gemm的话,采用单流执行,则具体的硬件执行调度如下所示: gemm0,gemm1,gemm2同时分配任务给sm做 第一轮:gemm0、gemm1和gemm2的0-4分给4个sm去做,都计算完成 第二轮:gemm0在上一轮已经计算完毕,gemm1和gemm2的45分给sm0,sm1去做,在这一轮…

RelationMap图谱--VUE,真实项目提供mock数据

RelationMap官网&#xff1a; 在线配置官网&#xff08;可以把数据放进去&#xff0c;直接看效果&#xff09; VUE2 效果&#xff1a;左侧列表栏&#xff0c;点击右侧显示对应的图谱 代码&#xff1a;按照代码直接贴过去&#xff0c;直接出效果 relationMap/index.vue <te…

1W、2W 3KVAC隔离 宽电压输入 交直两用AC/DC 电源模块 ——TP01(02)AZ 系列

TP01(02)AZ为客户提供一款超小体积模块式开关电源&#xff0c;该系列模块电源输出功率为1W、2W&#xff0c;具有极低的空载损耗&#xff0c;低漏电流仅0.1mA&#xff0c;小体积&#xff0c;隔离耐压高达3KV等特点。产品安全可靠&#xff0c;EMC 性能好&#xff0c;EMC 及安全规…

MQTT 5.0 报文解析 04:PINGREQ 与 PINGRESP

欢迎阅读 MQTT 5.0 报文系列 的第四篇文章。在上一篇中&#xff0c;我们已经介绍了 MQTT 5.0 中的 SUBSCRIBE 报文和 UNSUBSCRIBE 报文。现在&#xff0c;我们将介绍用于维持连接的控制报文&#xff1a;PINGREQ 和 PINGRESP。 除了用于连接、发布和订阅的控制报文&#xff0c;…

【Linux基础】Vim保姆级一键配置教程(手把手教你把Vim打造成高效率C++开发环境)

目录 一、前言 二、安装Vim 三、原始Vim编译器的缺陷分析 四、Vim配置 &#x1f95d;预备知识----.vimrc 隐藏文件 &#x1f34b;手动配置 Vim --- &#xff08;不推荐&#xff09; &#x1f347;自动化一键配置 Vim --- (强烈推荐) ✨功能演示 五、共勉 一、前言 Vim作为…

半小时搞懂STM32面经知识——ADC

1.ADC 1.1 ADC是什么&#xff1f; 将连续变量的模拟信号转换为离散变量的数字信号 1.2 ADC的位数&#xff1f;&#xff08;采样精度&#xff09; F1和F4都具有3个ADC&#xff0c;F1可提供21个输入通道&#xff0c;F4可以提供24个输入通道。 F4的ADC支持12位&#xff0c;10位…

3W 3KVAC隔离 宽电压输入 AC/DC 电源模块——TP03AC 系列

TP03AC系列电源模块额定输出功率为3W&#xff0c;此系列产品输入电压范围宽&#xff0c;可以交直流两用。并具备高可靠性、高精度、更安全、更稳定&#xff0c;大功率密度&#xff0c;超小体积&#xff0c;无需外加散热器&#xff0c;输出电压稳定等特点&#xff0c;且均集成有…

代码大师的工具箱:现代软件开发利器

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

2024年可以做的网上兼职有哪些?10个正规赚钱软件平台分享

在数字化浪潮席卷全球的今天&#xff0c;兼职工作早已不再局限于传统的线下模式。只要有一部手机或电脑&#xff0c;你就能轻松开启兼职之旅&#xff0c;实现躺着也能赚钱的梦想&#xff01; 接下来&#xff0c;就让我们一起看看2024年那些靠谱又有趣的网上兼职项目吧&#xff…

制造业精益生产KPI和智慧供应链管理方案和实践案例分享

随着工业4.0的推进和国家对制造业高质量发展的重视&#xff0c;工业数据已跃升为生产经营活动中不可或缺的核心要素&#xff0c;同时&#xff0c;工业数据也是形成新质生产力的优质生产要素&#xff0c;助力企业实现高效精益生产。 工业数据在制造业中的作用不可忽视&#xff…

汇聚荣科技:拼多多开店时后期押金可以退吗?

在电商领域&#xff0c;拼多多以其独特的团购模式迅速崛起&#xff0c;吸引了众多商家入驻。对于这些商家而言&#xff0c;了解平台的各项费用政策尤为重要&#xff0c;其中押金的退还问题是大家关注的焦点之一。那么&#xff0c;拼多多开店时后期押金可以退吗?答案是肯定的。…