一、什么是promise,及其作用 Promise
是ES6中的一个内置对象,实际是一个构造函数,是JS中进行异步编程的新的解决方案。
特点: ① 三种状态:pending
(进行中)、resolved
(已完成)、rejected
(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都不能改变这个状态。 ② 两种状态的转化:其一,从pending
(进行中)到resolved
(已完成)。其二,从pending
(进行中)到rejected
(已失败)。只有这两种形式的转变。 ③ Promise
构造函数的原型对象上,有then
()和catch
()等方法,then
()第一个参数接收resolved()
传来的数据,catch()
第一个参数接收rejected()
传来的数据
作用: ① 通常用来解决异步调用问题 ② 解决多层回调嵌套的方案 ③ 提高代码可读性、更便于维护
二 、Promise 基本流程
三、Promise的链式调用 我们可以用 promise.then()
,promise.catch()
和 promise.finally()
这些方法将进一步的操作与一个变为已敲定状态的 promise
关联起来。这些方法还会返回一个新生成的 promise
对象,这个对象可以被非强制性的用来做链式调用,就像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA,handleRejectedA) .then(handleFulfilledB,handleRejectedB) .then(handleFulfilledC,handleRejectedC); // 或者,这样可能会更好... const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA) .then(handleFulfilledB) .then(handleFulfilledC) .catch(handleRejectedAny);
过早地处理被拒绝的 promise
会对之后promise
的链式调用造成影响。不过有时候我们因为需要马上处理一个错误也只能这样做。另一方面,在没有迫切需要的情况下,可以在最后一个.catch()
语句时再进行错误处理,这种做法更加简单。
四、Promise 的基本使用 示例,如果当前时间是偶数就代表成功,否则代表失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 1. 创建一个新的Promise对象 const p = new Promise((resolve, reject) => { // 执行器函数,同步执行 // 2. 执行异步操作任务 setTimeout(() => { const time = Date.now() // 如果当前时间是偶数就代表成功,否则代表失败 // 3.1 如果成功了,调用resolve(value) if (time % 2 === 0) { resolve('成功的数据,value = ' + time) } else { // 3.2 如果失败了,调用reject(reason) reject('失败的数据,reason = ' + time) } }, 1000); }) p.then(value => { // 接受得到成功的value数据,专业术语:onResolved console.log('成功的回调', value) }, reason => { // 接受得到失败的reason数据,专业术语:onRejected console.log('失败的回调', reason) })
五、Promise几种常用方法 5.1 Promise.all() 将多个Promise封装成一个新的Promise,成功时返回的是一个结果数组,失败时,返回的是最先rejected状态的值。
使用场景:一次发送多个请求并根据请求顺序获取和使用数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 let promise1 = Promise.resolve(1); let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise2'); }, 2000); }) let promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise3'); }, 1000); }) let promise4 = new Promise((resolve, reject) => { setTimeout(() => { reject('promise4'); }, 1000); }) let promise5 = Promise.reject('promise5'); Promise.all([promise1, promise2, promise3]).then((values) => { //三个promise都是成功的,所以会输出一个数组 console.log(values) // [1, "promise2", "promise3"] }) Promise.all([promise1, promise2, promise3, promise4]).then((values) => { console.log(values) }, (reason) => { //promise4是失败的,所以只会返回promise4的失败结果 console.log(reason) //promise4 }) Promise.all([promise1, promise2, promise3, promise4, promise5]).then((values) => { console.log(values) }, (reason) => { //promise4个promise5都是失败的,但是promise5比promise4最先失败,所以返回promise5的失败结果 console.log(reason) //promise5 })
5.2 Promise.race() 返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
简单来说,就是多个Promise中,哪个状态先变为成功或者失败,就返回哪个Promise的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 let promise1 = new Promise((resolve, reject) => { //setTimeout第三个以后的参数是作为第一个`func()`的参数传进去,promise1作为resolve的参数 setTimeout(resolve, 500, 'promise1'); }); let promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'promise2'); }); let promise3 = new Promise((resolve, reject) => { setTimeout(reject, 500, 'promise3'); }); let promise4 = new Promise((resolve, reject) => { setTimeout(reject, 100, 'promise4'); }); Promise.race([promise1, promise2]).then((value) => { console.log(value); //promise2 100 <promise1 500 所以输出: promise2 }); Promise.race([promise3, promise4]).then((value) => { }, (rejected) => { console.log(rejected) //promise4 100 < promise3 500 所以输出: promise4 }); Promise.race([promise2, promise3]).then((value) => { console.log(value) //promise2 100 < promise3 500 所以会走成功状态,输出: promise2 }, (rejected) => { console.log(rejected) }); Promise.race([promise1, promise4]).then((value) => { console.log(value) }, (rejected) => { console.log(rejected) //promise4 100 < promise1 500 所以会走失败状态,输出: promise4 });
5.3 Promise.any() Promise.any()
接收一个Promise
可迭代对象,只要其中的一个 promise
成功,就返回那个已经成功的 promise
。如果可迭代对象中没有一个 promise
成功(即所有的 promises
都失败/拒绝),就返回一个失败的 promise
和AggregateError
类型的实例,它是 Error
的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()
是相反的。
这个方法用于返回第一个成功的 promise
。只要有一个 promise
成功此方法就会终止,它不会等待其他的 promise
全部完成。 不像 Promise.all()会返回一组完成值那样(resolved values),我们只能得到一个成功值(假设至少有一个 promise
完成)。当我们只需要一个 promise
成功,而不关心是哪一个成功时此方法很有用的。 同时, 也不像 Promise.race()总是返回第一个结果值(resolved/reject)那样,这个方法返回的是第一个* 成功的* 值。这个方法将会忽略掉所有被拒绝的 promise
,直到第一个 promise
成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const pErr = new Promise((resolve, reject) => { reject("总是失败"); }); const pSlow = new Promise((resolve, reject) => { setTimeout(resolve, 500, "最终完成"); }); const pFast = new Promise((resolve, reject) => { setTimeout(resolve, 100, "很快完成"); }); Promise.any([pErr, pSlow, pFast]).then((value) => { console.log(value); // pFast fulfils first }) // 期望输出: "很快完成"
5.4 Promise.allSettled() Promise.allSettled()
方法返回一个在所有给定的promise都已经fulfilled
或rejected
后的promise,并带有一个对象数组,每个对象表示对应的promise结果。 当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise
的结果时,通常使用它。 相比之下,Promise.all()
更适合彼此相互依赖或者在其中任何一个reject
时立即结束。
1 2 3 4 5 6 7 8 9 10 const promise1 = Promise.resolve(3); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); const promises = [promise1, promise2]; Promise.allSettled(promises). then((results) => results.forEach((result) => console.log(result.status))); // expected output: // "fulfilled" // "rejected"
六、什么是Async/Await,及其作用 ① async/await是ES7新特性 ② async/await是写异步代码的新方式,以前的方法有回调函数和Promise ③ async/await是基于Promise实现的,它不能用于普通的回调函数 ④ async/await与Promise一样,是非阻塞的 ⑤ async/await使得异步代码看起来像同步代码,这正是它的魔力所在
async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果,。如果你在代码中使用了异步函数,就会发现它的语法和结构会更像是标准的同步函数。
await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用。
6.1 async 函数 async 函数的返回值为 Promise 对象,async 函数返回的 Promise 的结果由函数执行的结果决定
1 2 3 4 5 async function fn1() { return 1 } const result = fn1() console.log(result) // Promise { 1 }
既然是 Promise 对象,那么我们用 then 来调用,并抛出错误,执行 **onRejected() **且 reason 为错误信息为“我是错误”
1 2 3 4 5 6 7 8 9 10 async function fn1() { // return 1 // return Promise.resolve(1) // return Promise.reject(2) throw '我是错误' } fn1().then( value => { console.log('onResolved()', value) }, reason => { console.log('onRejected()', reason) } // onRejected() 我是错误 )
6.2 await 表达式 await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
如果表达式是 promise 对象, await 返回的是 promise 成功的值
如果表达式是其它值, 直接将此值作为 await 的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function fn2() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }) } function fn4() { return 6 } async function fn3() { // const value = await fn2() // await 右侧表达式为Promise,得到的结果就是Promise成功的value // const value = await '还可以这样' const value = await fn4() console.log('value', value) } fn3() // value 6
await 必须写在 async 函数中, 但 async 函数中可以没有 await ,如果 await 的 Promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function fn2() { return new Promise((resolve, reject) => { setTimeout(() => { // resolve(1000) reject(1000) }, 1000); }) } async function fn3() { try { const value = await fn2() } catch (error) { console.log('得到失败的结果', error) } } fn3() // 得到失败的结果 1000
七、区别: 1)简洁的代码 使用async函数可以让代码简洁很多,不需要像Promise一样需要些then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。
2) 错误处理: Promise 中不能自定义使用 try/catch 进行错误捕获,但是在 Async/await 中可以像处理同步代码处理错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //#Promise const makeRequest = () => { try { getJSON() .then(result => { // this parse may fail const data = JSON.parse(result) console.log(data) }) // uncomment this block to handle asynchronous errors // .catch((err) => { // console.log(err) // }) } catch (err) { console.log(err) } } //#Async/await const makeRequest = async () => { try { // this parse may fail const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }
3)条件语句 条件语句也和错误捕获是一样的,在 Async 中也可以像平时一般使用条件语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //#Promise const makeRequest = () => { return getJSON() .then(data => { if (data.needsAnotherRequest) { return makeAnotherRequest(data) .then(moreData => { console.log(moreData) return moreData }) } else { console.log(data) return data } }) } //#Async const makeRequest = async () => { const data = await getJSON() if (data.needsAnotherRequest) { const moreData = await makeAnotherRequest(data); console.log(moreData) return moreData } else { console.log(data) return data } }
4)中间值 在一些场景中,也许需要 promise1 去触发 promise2 再去触发 promise3,这个时候代码应该是这样的
1 2 3 4 5 6 7 8 9 10 11 const makeRequest = () => { return promise1() .then(value1 => { // do something return promise2(value1) .then(value2 => { // do something return promise3(value1, value2) }) }) }
如过 promise3 不需要 value1,嵌套将会变得简单。如果你有强迫症,则将值1&2使用 promise.all() 分装起来。
1 2 3 4 5 6 7 8 9 10 11 const makeRequest = () => { return promise1() .then(value1 => { // do something return Promise.all([value1, promise2(value1)]) }) .then(([value1, value2]) => { // do something return promise3(value1, value2) }) }
但是使用 Async 就会变得很简单
1 2 3 4 5 const makeRequest = async () => { const value1 = await promise1() const value2 = await promise2(value1) return promise3(value1, value2) }
错误栈 如过 Promise 连续调用,对于错误的处理是很麻烦的。你无法知道错误出在哪里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const makeRequest = () => { return callAPromise() .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => { throw new Error("oops"); }) } makeRequest() .catch(err => { console.log(err); // output // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13) })
但是对于 Async 就不一样了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const makeRequest = async () => { await callAPromise() await callAPromise() await callAPromise() await callAPromise() await callAPromise() throw new Error("oops"); } makeRequest() .catch(err => { console.log(err); // output // Error: oops at makeRequest (index.js:7:9) })
6)调试 async/await能够使得代码调试更简单。2个理由使得调试Promise变得非常痛苦:
《1》不能在返回表达式的箭头函数中设置断点
《2》如果你在.then代码块中设置断点,使用Step Over快捷键,调试器不会跳到下一个.then,因为它只会跳过异步代码。
使用await/async时,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过await语句。