Open toFrankie opened 1 year ago
我认为 Promise 应该算是 ES6 标准最大的亮点,它提供了异步编程的一种解决方案。比传统的回调函数和事件解决方案,它更合理、更强大。
Promise 是一个容器,里面保存着某个未来才会结束的事件(一般为异步操作)的结果。从语法上来说,Promise 是一个对象,它可以获取异步操作的消息。
Promise
Promise 对象的特点:
Promise 对象有且只有三种状态:pending、fulfilled、rejected,分别表示进行中、已成功、已失败。
pending
fulfilled
rejected
一旦状态发生改变,就不会再变。状态的改变只有两种可能:pending -> fulfilled 或 pending -> rejected。若发生了其中一种情况,状态就会一直保存这个结果,这时就成为 resolved(已定型)。
pending -> fulfilled
pending -> rejected
resolved
这种以“同步的方式”去表达异步流程,可以避免层层嵌套的回调函数,避免出现“回调地狱”(Callback Hell)。
BTW,网上有些文章把 fulfilled 状态,叫成 resolved,尽管我们可能知道他想表达的意思,但其实是不对的。
Promise 对象的缺点:
一是无法取消 Promise,一旦创建它就会立即执行,无法中途取消;二是若不设置回调函数情况下,Promise 内部抛出错误,不会反馈到外部;三是当处于 pending 状态,无法得知目前进展到哪个阶段。
根据规定,Promise 是一个构造函数,用来生成 Promise 实例对象。
示例:
const handler = (resolve, reject) => { // some statements... // 根据异步操作的结果,通过 resolve 或 reject 函数去改变 Promise 对象的状态 if (true) { // pending -> fulfilled resolve(...) } else { // pending -> rejected reject(...) } // 需要注意的是: // 1. 在上面 Promise 状态已经定型(fulfilled 或 rejected), // 因此,我们再使用 resolve() 或 reject() 或主动/被动抛出错误的方式, // 试图再次修改状态,是没用的,状态不会再发生改变。 // 2. 当 Promise 对象的状态“已定型”后,若未使用 return 终止代码往下执行, // 后面代码出现的错误(主动抛出或语法错误等),在外部都不可见,无法捕获到。 // 3. hander 函数的返回值是没意义的。怎么理解? // 假设内部不包括 resolve() 或 reject() 或内部不出现语法错误, // 或不主动抛出错误,仅有类似 `return 'anything'` 语句, // 那么 promise 对象永远都是 pending 状态。 } const promise = new Promise(handler)
Promie 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。而 resolve 和 rejeact 也是函数,其作用是改变 Promise 对象的状态,分别是 pending -> fulfilled 和 pending -> rejected。
Promie
resolve
reject
rejeact
假设构造函数内不指定 resolve 或 reject 函数,那么 Promise 的对象会一直保持着 pending 待定的状态。
Promise 实例生成以后,当 Promise 内部状态发生变化,可以使用 Promise.prototype.then() 方法获取到。
Promise.prototype.then()
const success = res => { // 当状态从 pending 到 fulfilled 时,执行此函数 // some statements... } const fail = err => { // 当状态从 pending 到 rejected 时,执行此函数 // some statements... } promise.then(success, fail)
then() 方法接受两个回调函数作为参数,第一个回调函数在 Promise 对象状态变为 fulfilled 时被调用。第二回调函数在状态变为 rejected 时被调用。then() 方法的两个参数都是可选的。
then()
注意,由于 Promise 实例对象的 Promise.prototype.then()、Promise.prototype.catch()、Promise.prototype.finally() 方法属于异步任务中的微任务。注意它们的执行时机,会在当前同步任务执行完之后,且在下一次宏任务执行之前,被执行。 还有,Promise 构造函数(即上述示例的 handler 函数)内部,仍属于同步任务,而非异步任务。 所以,那个经典的面试题就是,包括 setTimeout、Promise 等,然后问输出顺序是什么?本质就是考察 JavaScript 的事件循环机制(Event Loop)嘛。这块内容可以看下文章:JavaScript 事件循环。
注意,由于 Promise 实例对象的 Promise.prototype.then()、Promise.prototype.catch()、Promise.prototype.finally() 方法属于异步任务中的微任务。注意它们的执行时机,会在当前同步任务执行完之后,且在下一次宏任务执行之前,被执行。
Promise.prototype.catch()
Promise.prototype.finally()
还有,Promise 构造函数(即上述示例的 handler 函数)内部,仍属于同步任务,而非异步任务。
handler
所以,那个经典的面试题就是,包括 setTimeout、Promise 等,然后问输出顺序是什么?本质就是考察 JavaScript 的事件循环机制(Event Loop)嘛。这块内容可以看下文章:JavaScript 事件循环。
setTimeout
插了个话题,回来
then() 方法的两个参数 success()、fail(),它们接收的实参就是传递给 resolve() 和 reject() 的值。
success()
fail()
resolve()
reject()
例如:
function timeout(delay, status = true) { const promise = new Promise((resolve, reject) => { setTimeout(() => { // 一般 reject 应返回一个 Error 实例对象,如:new Error('Oops') status ? resolve('Success') : reject('Oops') }) }, delay) return promise } // 创建两个 Promise 实例对象 const p1 = timeout(1000) const p2 = timeout(1000, false) // pending -> fulfilled p1.then(res => { console.log(res) // "Success" }) // pending -> rejected p2.then(null, err => { console.warn(err) // "Oops" })
上面示例中,根据 timeout 函数的逻辑,p1 实例的 Promise 状态会从 pending -> fulfilled,而 p2 实例则是从 pending -> rejected。因此会分别打印出 "Success"、"Oops"。
timeout
p1
p2
"Success"
"Oops"
例如,异步加载图片的例子。
function loadImage(url) { return new Promise((resolve, reject) => { const image = new Image() image.onload = function () { resolve(image) } image.onerror = function () { reject(new Error(`Could not load image at ${url}.`)) } image.src = url }) } loadImage('https://jquery.com/jquery-wp-content/themes/jquery/images/logo-jquery@2x.png') .then( res => { console.log('Image loaded successfully:', res) }, err => { console.warn(err) } )
因此,Promise 的用法还是很简单的,是预期结果的话,使用 resolve() 修改状态为 fulfilled,非预期结果使用 reject() 修改状态为 rejected。具体返回值根据实际场景返回就好。
在构建 Promise 对象的内部,使用 resolve() 或 reject() 去改变 Promise 的状态,并不会终止 resolve 或 reject 后面代码的执行。
const promise = new Promise((resolve, reject) => { resolve(1) // 以下代码仍会执行,且会在 then 之前执行。 // reject() 同理。 console.log(2) }) promise.then(res => { console.log(res) }) // 先后打印出 2、1
若要终止后面的执行,只要使用 return 关键字即可,类似 return resolve(1) 或 return reject(1)。但如果这样,其实后面的代码就没意义,因此也就没必要写了。千万别在工作中写出这样的代码,我怕你被打。这里只是为了说明 resolve 或 reject 不会终止后面的代码执行而已。
return
return resolve(1)
return reject(1)
一般来说,调用 resolve() 或 reject() 说明异步操作有了结果,那么 Promise 的使命就完成了,后续的操作应该是放到 then() 方法里面,而不是放在 resolve() 或 reject() 后面。
在前面的示例中,resolve() 或 reject() 都是返回一个“普通值”。如果我们返回一个 Promise 对象,会怎样呢?
首先,它是允许返回一个 Promise 对象的,但是有些区别。
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p1 success') // reject('p1 fail') }, 3000) }) const p2 = new Promise((resolve, reject) => { // 这时返回一个 Promise 对象 // ⚠️ 注意,这里 resolve(p1) 或 reject(p1) 执行的逻辑会有所不同。 resolve(p1) // reject(p1) }) p2.then( res => { console.log('p2 then:', res) }, err => { console.log('p2 catch:', err) } )
分析如下:
1. 若 p2 内里面 `resolve(p1)` 时: 当代码执行到 `resolve(p1)` 时,由于 p1 的状态仍是 pending, 这时 p1 的状态会传递给 p2,也就是说 p1 的状态决定了 p2 的状态, 因此 `p2.then()` 需要等 p1 的状态发生变化,才会被调用, 且 `p2.then()` 获取到的状态就是 p1 的状态 假设代码执行到 `resolve(p1)` 时,若 p1 的状态已定型,即 fulfilled 或 rejected, 会立即调用 `p2.then()` 方法。 PS:这里“立即”是指,当前同步任务已执行完毕的前提下。第 2 点也是如此。 2. 若 p2 内是 `reject(p1)` 时,情况会有所不同: 当代码执行到 `reject(p1)` 时,由于 p2 的状态会变更为 rejected, 接着会立即调用 `p2.then()` 方法,由于是 rejected 状态, 因此,会触发 `p2.then()` 的第二个参数,此时 err 的值就是 p1(一个 Promise 对象)。 假设 p1 的状态最终变成了 rejected,那么 err 还要捕获异常, 例如 `err.catch(err => { /* do something... */ })`, 否则的话,在控制台会报错,类似:"Uncaught (in promise) p1 fail", 原因就是 Promise 对象的 rejected 状态未处理,导致的。 假设 p1 的状态最终变成 fulfilled,那么不需要做上一步类似的处理。
上面两种情况,其实相当于 Promise.resolve(p1)、Promise.reject(p1)。我们来打印一下两种结果:
Promise.resolve(p1)
Promise.reject(p1)
当 p1 状态为 fulfilled 时,p2 状态如图:
当 p1 状态为 rejected 时,p2 状态如图:
Promise 的实例具有 then() 方法,它是定义在原型对象 Promise.prototype 上的。当 Promise 实例对象的状态发生变化,此方法就会被触发调用。
Promise.prototype
前面提到 Promise.prototype.then() 接受两个参数,两者均可选,这里不再赘述。
then() 方法返回一个新的 Promise 实例对象(注意,不是原来那个 Promise 实例),也因此可以采用链式写法,即 then() 方法后面可以再调用另一个 then() 方法。
例如,以下示例使用 Fetch API 进行网络请求:
window.fetch('/config') .then(response => response.json()) .then( res => { // do something... }, err => { // do something... } ) // .then() // ...
以上链式调用,会按照顺序调用回调函数,后一个 then() 的执行,需等到前一个 Promise 对象的状态定型。
Promise.prototype.catch() 方法是 then(null, rejection) 或 then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。
then(null, rejection)
then(undefined, rejection)
同样地,它会返回一个新的 Promise 实例对象。
const promise = new Promise((resolve, reject) => { reject('Oops') // 或通过 throw 方式主动抛出错误,使其变成 rejected 状态 // 但注意的是,前面状态“定型”之后,状态是不会再变的。 // 这后面试图改变状态,或主动抛出错误,或出现其他语法错误, // 不会被外部捕获到,即无意义。 }) promise.catch(err => { console.log(err) // "Oops" }) // 相当于 promise.then( null, err => { console.log(err) // "Oops" } )
通过 throw 等方式使其变成 rejected 状态,相当于:
throw
const promise = new Promise((resolve, reject) => { try { throw 'Oops' // 一般地,是抛出一个 Error(或派生)实例对象,如 throw new Error('Oops') } catch (e) { reject(e) } })
前面提到有两种方式,可以捕获 Promise 对象的 rejected 状态。那么孰优孰劣呢?
建议如下:
尽量不要在 Promise.prototype.then() 方法里面定义 onRejection 回调函数(即 then() 的第二个参数),总使用 Promise.prototype.catch() 方法。
onRejection
const promise = new Promise((resolve, reject) => { // some statements }) // bad promise.then( res => { /* some statements */ }, err => { /* some statements */ } ) // good promise .then(res => { /* some statements */ }) .catch(err => { /* some statements */ })
上面示例中,第二种写法要好于第一种写法。理由是第二种写法可以捕获前面 then() 方法中的异常或错误,也更接近同步写法(try...catch)。因此,建议总是使用 Promise.prototype.catch() 方法。
try...catch
与传统的 try...catch 代码块不同的是,即使 Promise 内部出现错误,也不会影响 Promise 外部代码的执行。
const promise = new Promise((resolve, reject) => { say() // 这行会报错:ReferenceError: say is not defined }) promise.then(res => { /* some statements */ }) setTimeout(() => { console.log(promise) // 这里仍会执行,打印出 promise 实例对象 })
上面的示例中,在 Promise 内部就会发生引用错误,因为 say 函数并没有定义,但并未终止脚本的执行。接着还会输出 promise 对象。也就是说,Promise 内部的错误并不会影响到 Promise 外部代码,通俗的说法就是“Promise 会吃掉错误”。
say
promise
但是,如果脚本放在服务器上执行,退出码就是 0(表示执行成功)。不过 Node.js 有一个 unhandledRejection 事件,它专门监听未捕获的 reject 错误,脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。如下:
0
unhandledRejection
注意,Node.js 有计划在未来废除 unhandledRejection 事件。如果 Promise 内部由未捕获的错误,会直接终止进程,并且进程的退出码不为 0。
在 catch() 方法中,也可以抛出错误。而且由于 then() 和 catch() 方法均返回一个新的 Promise 实例对象,因此可以采用链式写法,写出一系列的...
catch()
const promise = new Promise((resolve, reject) => { reject('Oops') }) promise .then(res => { /* some statements */ }) .catch(err => { throw new Error('Oh...') }) .catch(err => { /* 这里可以捕获上一个 rejected 状态 */ }) // ... 还可以写一系列的 then、catch 方法
在 ES9 标准中,引入了 Promise.prototype.finally() 方法,用于指定 Promise 对象状态发生改变(不管 fulfilled 还是 rejected)后,都会触发此方法。
const promise = new Promise((resolve, reject) => { // some statements }) promise .then(res => { /* some statements */ }) .catch(err => { /* some statements */ }) .finally(() => { // do something... // 注意,finally 不接受任何参数,自然也无法得知 Promise 对象的状态。 })
若 Promise 内部不写任何 resovle()、或 rejected()、或无任何语法错误(如上述示例),Promise 实例对象的状态并不会发生变化,即一直都是 pending 状态,它都不会触发 then()、catch()、finally() 方法。这点就怕有人会误解,状态不发生变化时也会触发 finally() 方法,这是错的。
resovle()
rejected()
finally()
Promise.prototype.finally() 也是返回一个新的 Promise 实例对象,而且该实例对象的值,就是前面一个 Promise 实例对象的值。
const p1 = new Promise(resolve => resolve(1)) const p2 = p1.then().finally() const p3 = p1.then(() => { }).finally() const p4 = p1.then(() => { return true }).finally() const p5 = p1.then(() => { throw 'Oops' /* 当然这里没处理 rejected 状态 */ }).finally() const p6 = p1.then(() => { throw 'Oh...' }).catch(err => { return 'abc' }).finally() const p7 = p1.finally(() => { return 'finally' }) const p8 = p1.finally(() => { throw 'error' }) setTimeout(() => { console.log('p1:', p1) console.log('p2:', p2) console.log('p3:', p3) console.log('p4:', p4) console.log('p5:', p5) console.log('p6:', p6) console.log('p7:', p7) console.log('p8:', p8) }) // 解释一下 `p1` 和 `p1.then()`: // 当 `then()` 方法中不写回调函数时,会发生值的穿透, // 即 `p1.then()` 返回的新实例对象(假设为 `x`)的值跟 p1 实例的值是一样的, // 但注意 `p1` 和 `x` 是两个不同的 Promise 实例对象。 // 关于值穿透的问题,后面会给出示例。
根据打印结果可以验证: finally() 方法返回的 Promise 实例对象的值与前一个 Promise 实例对象的值是相等的,但尽管如此,两者是两个不同的 Promise 实例对象。可以打印一下 p1 === p7,比较结果为 false。
p1 === p7
false
关于 Promise.prototype.finally() 的实现,如下:
Promise.prototype.finally = function (callback) { let P = this.constructor return this.then( value => P.resolve(callback && callback()).then(() => value), reason => P.resolve(callback && callback()).then(() => { throw reason }) ) }
关于 Promise.prototype.then()、Promise.prototype.catch()、Promise.prototype.finally() 方法,总结以下特点:
三者均返回一个全新的 Promise 实例对象。 即使 then()、catch()、finally() 方法在不指定回调函数的情况下,仍会返回一个全新的 Promise 实例对象,但此时会出现“值穿透”的情况,即实例值为前一个实例的值。 假设三者的回调函数中无语法错误(包括不使用 throw 关键字) 时,then() 和 catch() 方法返回的实例对象的值,依靠 return 关键字来指定,否则为 undefined。 而 finally() 方法稍有不同,即使使用了 return 也是无意义的,因为它返回的 Promise 实例对象的值总是前一个 Promise 实例的值。 三个方法的返回操作 return any,相当于 Promise.resolve(any)(这里 any 是指任何值)。 当 then()、catch()、finally() 方法中出现语法错误或者利用 throw 关键字主动抛出错误,它们返回的 Promise 实例对象的状态会变成 rejected,而且实例对象的值就是所抛出的错误原因。 Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch() 方法捕获。
三者均返回一个全新的 Promise 实例对象。
即使 then()、catch()、finally() 方法在不指定回调函数的情况下,仍会返回一个全新的 Promise 实例对象,但此时会出现“值穿透”的情况,即实例值为前一个实例的值。
假设三者的回调函数中无语法错误(包括不使用 throw 关键字) 时,then() 和 catch() 方法返回的实例对象的值,依靠 return 关键字来指定,否则为 undefined。
undefined
而 finally() 方法稍有不同,即使使用了 return 也是无意义的,因为它返回的 Promise 实例对象的值总是前一个 Promise 实例的值。
三个方法的返回操作 return any,相当于 Promise.resolve(any)(这里 any 是指任何值)。
return any
Promise.resolve(any)
any
当 then()、catch()、finally() 方法中出现语法错误或者利用 throw 关键字主动抛出错误,它们返回的 Promise 实例对象的状态会变成 rejected,而且实例对象的值就是所抛出的错误原因。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch() 方法捕获。
关于值的穿透,请看示例:
const person = { name: 'Frankie' } // 使用引用值更能说明问题 const p1 = new Promise(resolve => resolve(person)) const p2 = new Promise((resolve, reject) => reject(person)) // 情况一:fulfilled p1.then(res => { console.log(res === person) // true }) // 情况二:fulfilled p1 .then() .then(res => { console.log(res === person) // true }) // 情况三:rejected p2 .catch() .then(res => { /* 不会触发 then */ }) .catch(err => { console.log(err === person) // true }) // 情况四:fulfilled p1 .finally() .then(res => { console.log(res === person) // true })
从结果上看,尽管三者在不指定回调函数的情形下,“似乎”是不影响结果的。但前面提到 p1 跟 p1.then()、p1.catch()、p1.finally() 都是两个不同的 Promise 实例对象,尽管这些实例对象的值是相等的。
p1.then()
p1.catch()
p1.finally()
在实际应用场景中,我们应该避免写出这些“无意义”的代码。但是我们在去学习它们的时候,应该要知道。就是“用不用”和“会不会”是两回事。
下一篇接着介绍 Promise.all()、Promise.race() 等,未完待续...
Promise.all()
Promise.race()
我认为 Promise 应该算是 ES6 标准最大的亮点,它提供了异步编程的一种解决方案。比传统的回调函数和事件解决方案,它更合理、更强大。
一、简介
Promise 是一个容器,里面保存着某个未来才会结束的事件(一般为异步操作)的结果。从语法上来说,
Promise
是一个对象,它可以获取异步操作的消息。Promise
对象的特点:Promise
对象有且只有三种状态:pending
、fulfilled
、rejected
,分别表示进行中、已成功、已失败。一旦状态发生改变,就不会再变。状态的改变只有两种可能:
pending -> fulfilled
或pending -> rejected
。若发生了其中一种情况,状态就会一直保存这个结果,这时就成为resolved
(已定型)。这种以“同步的方式”去表达异步流程,可以避免层层嵌套的回调函数,避免出现“回调地狱”(Callback Hell)。
Promise
对象的缺点:一是无法取消
Promise
,一旦创建它就会立即执行,无法中途取消;二是若不设置回调函数情况下,Promise
内部抛出错误,不会反馈到外部;三是当处于pending
状态,无法得知目前进展到哪个阶段。二、Promise 用法
根据规定,
Promise
是一个构造函数,用来生成Promise
实例对象。1. 创建 Promise 对象
示例:
Promie
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。而resolve
和rejeact
也是函数,其作用是改变Promise
对象的状态,分别是pending -> fulfilled
和pending -> rejected
。假设构造函数内不指定
resolve
或reject
函数,那么Promise
的对象会一直保持着pending
待定的状态。2. Promise 实例
Promise
实例生成以后,当Promise
内部状态发生变化,可以使用Promise.prototype.then()
方法获取到。then()
方法接受两个回调函数作为参数,第一个回调函数在Promise
对象状态变为fulfilled
时被调用。第二回调函数在状态变为rejected
时被调用。then()
方法的两个参数都是可选的。插了个话题,回来
then()
方法的两个参数success()
、fail()
,它们接收的实参就是传递给resolve()
和reject()
的值。例如:
上面示例中,根据
timeout
函数的逻辑,p1
实例的Promise
状态会从pending -> fulfilled
,而p2
实例则是从pending -> rejected
。因此会分别打印出"Success"
、"Oops"
。例如,异步加载图片的例子。
因此,
Promise
的用法还是很简单的,是预期结果的话,使用resolve()
修改状态为fulfilled
,非预期结果使用reject()
修改状态为rejected
。具体返回值根据实际场景返回就好。3. Promise 注意事项
在构建
Promise
对象的内部,使用resolve()
或reject()
去改变Promise
的状态,并不会终止resolve
或reject
后面代码的执行。例如:
若要终止后面的执行,只要使用
return
关键字即可,类似return resolve(1)
或return reject(1)
。但如果这样,其实后面的代码就没意义,因此也就没必要写了。千万别在工作中写出这样的代码,我怕你被打。这里只是为了说明resolve
或reject
不会终止后面的代码执行而已。在前面的示例中,
resolve()
或reject()
都是返回一个“普通值”。如果我们返回一个Promise
对象,会怎样呢?首先,它是允许返回一个
Promise
对象的,但是有些区别。分析如下:
上面两种情况,其实相当于
Promise.resolve(p1)
、Promise.reject(p1)
。我们来打印一下两种结果:当
p1
状态为fulfilled
时,p2
状态如图:当
p1
状态为rejected
时,p2
状态如图:三、Promise.prototype.then()
Promise
的实例具有then()
方法,它是定义在原型对象Promise.prototype
上的。当Promise
实例对象的状态发生变化,此方法就会被触发调用。前面提到
Promise.prototype.then()
接受两个参数,两者均可选,这里不再赘述。then()
方法返回一个新的Promise
实例对象(注意,不是原来那个Promise
实例),也因此可以采用链式写法,即then()
方法后面可以再调用另一个then()
方法。例如,以下示例使用 Fetch API 进行网络请求:
以上链式调用,会按照顺序调用回调函数,后一个
then()
的执行,需等到前一个Promise
对象的状态定型。四、Promise.prototype.catch()
Promise.prototype.catch()
方法是then(null, rejection)
或then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。同样地,它会返回一个新的
Promise
实例对象。通过
throw
等方式使其变成rejected
状态,相当于:五、捕获 rejected 状态的两种方式比较
前面提到有两种方式,可以捕获
Promise
对象的rejected
状态。那么孰优孰劣呢?建议如下:
上面示例中,第二种写法要好于第一种写法。理由是第二种写法可以捕获前面
then()
方法中的异常或错误,也更接近同步写法(try...catch
)。因此,建议总是使用Promise.prototype.catch()
方法。与传统的
try...catch
代码块不同的是,即使 Promise 内部出现错误,也不会影响 Promise 外部代码的执行。上面的示例中,在 Promise 内部就会发生引用错误,因为
say
函数并没有定义,但并未终止脚本的执行。接着还会输出promise
对象。也就是说,Promise 内部的错误并不会影响到 Promise 外部代码,通俗的说法就是“Promise 会吃掉错误”。但是,如果脚本放在服务器上执行,退出码就是
0
(表示执行成功)。不过 Node.js 有一个unhandledRejection
事件,它专门监听未捕获的reject
错误,脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。如下:注意,Node.js 有计划在未来废除
unhandledRejection
事件。如果 Promise 内部由未捕获的错误,会直接终止进程,并且进程的退出码不为0
。在
catch()
方法中,也可以抛出错误。而且由于then()
和catch()
方法均返回一个新的Promise
实例对象,因此可以采用链式写法,写出一系列的...六、Promise.prototype.finally()
在 ES9 标准中,引入了
Promise.prototype.finally()
方法,用于指定Promise
对象状态发生改变(不管fulfilled
还是rejected
)后,都会触发此方法。Promise.prototype.finally()
也是返回一个新的Promise
实例对象,而且该实例对象的值,就是前面一个Promise
实例对象的值。根据打印结果可以验证:
finally()
方法返回的Promise
实例对象的值与前一个Promise
实例对象的值是相等的,但尽管如此,两者是两个不同的Promise
实例对象。可以打印一下p1 === p7
,比较结果为false
。关于
Promise.prototype.finally()
的实现,如下:七、总结
关于
Promise.prototype.then()
、Promise.prototype.catch()
、Promise.prototype.finally()
方法,总结以下特点:关于值的穿透,请看示例:
从结果上看,尽管三者在不指定回调函数的情形下,“似乎”是不影响结果的。但前面提到
p1
跟p1.then()
、p1.catch()
、p1.finally()
都是两个不同的Promise
实例对象,尽管这些实例对象的值是相等的。在实际应用场景中,我们应该避免写出这些“无意义”的代码。但是我们在去学习它们的时候,应该要知道。就是“用不用”和“会不会”是两回事。
下一篇接着介绍
Promise.all()
、Promise.race()
等,未完待续...