let myPromise1 = new Promise(() => {});
console.log('myPromise1 :>> ', myPromise1);
let myPromise2 = new Promise((resolve, reject) => {
let a = 1;
for (let index = 0; index < 5; index++) {
a++;
}
})
console.log('myPromise2 :>> ', myPromise2)
myPromise2.then(() => {
console.log("myPromise2执行了then");
})
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码中,someAsyncThing()函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
本系列的主题是 JavaScript 专题,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
通俗易懂的Promise知识点总结,检验一下你是否真的完全掌握了promise?
前言
Promise 想必大家都十分熟悉,想想就那么几个 api,可是你真的了解 Promise 吗?
本文就带大家彻底盘点promise~
Promise 简介
Promise 是一种处理异步代码(而不会陷入回调地狱)的方式。
多年来,promise 已成为语言的一部分(在 ES2015 中进行了标准化和引入),并且最近变得更加集成,在 ES2017 中具有了 async 和 await。
异步函数 在底层使用了 promise,因此了解 promise 的工作方式是了解 async 和 await 的基础。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)一个
Promise
必然处于以下几种状态之一:(pending)
: 初始状态,既没有被兑现,也没有被拒绝。(fulfilled)
: 意味着操作成功完成。(rejected)
: 意味着操作失败。Promise 如何运作
当 promise 被调用后,它会以处理中状态
(pending)
开始。 这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。被创建的 promise 最终会以被解决状态
(fulfilled)
或 被拒绝状态(rejected)
结束,并在完成时调用相应的回调函数(传给 then 和 catch)。◾ 为了让读者尽快对promise有一个整体的理解,我们先来看一段promise的代码:
输出结果为:
这里包含了四个知识点:
resolve
,Promise状态会变成fulfilled
,即 已完成状态reject
,Promise状态会变成rejected
,即 被拒绝状态第一次为准
,第一次成功就永久
为fulfilled
,第一次失败就永远状态为rejected
throw
的话,就相当于执行了reject
◾ 接下来看下面一段代码,学习新的知识点:
输出结果为:
这里包含了三个知识点:
pending
resolve
、reject
以及throw
的话,这个promise的状态也是pending
pending
状态下的promise不会执行回调函数then()
◾ 最后一点:
输出结果:
这个里包含了一个知识点:
Promise
对象传入一个执行函数,否则将会报错。不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:
Promise 很棒的一点就是链式调用。
创建 promise
Promise API 公开了一个 Promise 构造函数,可以使用
new Promise()
对其进行初始化:如你所见,promise 检查了 done 全局常量,如果为真,则 promise 进入被解决状态(因为调用了
resolve
回调);否则,则执行reject
回调(将 promise 置于被拒绝状态)。 如果在执行路径中从未调用过这些函数之一,则 promise 会保持处理中状态。使用
resolve
和reject
,可以向调用者传达最终的 promise 状态以及该如何处理。 在上述示例中,只返回了一个字符串,但是它可以是一个对象,也可以为null
。 由于已经在上述的代码片段中创建了 promise,因此它已经开始执行。一个更常见的示例是一种被称为 Promisifying 的技术。 这项技术能够使用经典的 JavaScript 函数来接受回调并使其返回 promise:
使用 promise
在上一个章节中,介绍了如何创建 promise。
现在,看看如何使用 promise。
运行
checkIfItsDone()
会指定当isItDoneYet
promise 被解决(在 then 调用中)或被拒绝(在 catch 调用中)时执行的函数。实例 promise 封装 AJAX
使用上面封装好的ajax发起一个请求:
Promise.resolve()
有时需要将现有对象转为 Promise 对象,
Promise.resolve()
方法就起到这个作用。上面代码将 jQuery 生成的对象,转为一个新的 Promise 对象。
Promise.resolve()
等价于下面的写法。Promise.resolve
方法的参数分成四种情况。◾ (1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
◾ (2)参数是一个thenable对象
thenable
对象指的是具有then
方法的对象,比如下面这个对象。Promise.resolve
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then
方法。上面代码中,
thenable
对象的then
方法执行后,对象p1
的状态就变为resolved
,从而立即执行最后那个then
方法指定的回调函数,输出 42。◾ (3)参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有
then
方法的对象,则Promise.resolve
方法返回一个新的Promise
对象,状态为resolved
。上面代码生成一个新的
Promise
对象的实例p
。由于字符串Hello
不属于异步操作(判断方法是字符串对象不具有then
方法),返回Promise
实例的状态从一生成就是resolved
,所以回调函数会立即执行。Promise.resolve
方法的参数,会同时传给回调函数。◾ (4)不带有任何参数
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象。所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用
Promise.resolve()
方法。上面代码的变量p就是一个 Promise 对象。
需要注意的是,立即
resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。上面代码中,
setTimeout(fn, 0)
在下一轮“事件循环”开始时执行,Promise.resolve()
在本轮“事件循环”结束时执行,console.log('one')
则是立即执行,因此最先输出。Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。上面代码生成一个 Promise 对象的实例
p
,状态为rejected
,回调函数会立即执行。注意,
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。上面代码中,
Promise.reject
方法的参数是一个thenable
对象,执行以后,后面catch
方法的参数不是reject
抛出的“出错了”这个字符串,而是thenable
对象。Promise.prototype.then()
Promise 实例具有
then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then
方法的第一个参数是resolved
状态的回调函数。如果该参数不是函数,则会在内部被替换为(x) => x
,即原样返回 promise 最终结果的函数;第二个参数(可选)是
rejected
状态的回调函数。如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。上面的代码使用
then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。采用链式的
then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。上面代码中,第一个
then
方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化。如果变为resolved
,就调用第一个回调函数,如果状态变为rejected
,就调用第二个回调函数。如果采用箭头函数,上面的代码可以写得更简洁。
前面介绍了
then
方法的参数和链式调用,下面详细介绍一下then
方法的返回值,也就是then
方法中的return
。当一个
Promise
完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值return
依据以下规则返回。如果then
中的回调函数:(return)
了一个值,那么then
返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。(return)
任何值,那么then
返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined
。then
返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。(return)
一个已经是接受状态的 Promise,那么then
返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。(return)
一个已经是拒绝状态的 Promise,那么then
返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。(return)
一个未定状态(pending
)的 Promise,那么then
返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。链式调用 promise.then()
then
方法返回一个 Promise 对象,其允许方法链,从而创建一个 promise 链。链式 promise 的一个很好的示例是 Fetch API,可以用于获取资源,且当资源被获取时将 promise 链式排队进行执行。
Fetch API 是基于 promise 的机制,调用
fetch()
相当于使用 new Promise() 来定义 promsie。在此示例中,调用
fetch()
从域根目录中的todos.json
文件中获取 TODO 项目的列表,并创建一个 promise 链。运行
fetch()
会返回一个响应response
,该响应具有许多属性,在属性中引用了:status
,表示 HTTP 状态码的数值。statusText
,状态消息,如果请求成功,则为 OK。response
还有一个json()
方法,该方法会返回一个 promise,该 promise 解决时会传入已处理并转换为 JSON 的响应体的内容。因此,考虑到这些前提,发生的过程是:链中的第一个 promise 是我们定义的函数,即
status()
,它会检查响应的状态,如果不是成功响应(介于 200 和 299 之间),则它会拒绝 promise。此操作会导致 promise 链跳过列出的所有被链的 promise,且会直接跳到底部的
catch()
语句(记录请求失败
的文本和错误消息)。如果成功,则会调用定义的
json()
函数。 由于上一个 promise 成功后返回了 response 对象,因此将其作为第二个 promise 的输入。在此示例中,返回处理后的 JSON 数据,因此第三个 promise 直接接收 JSON:
Promise.prototype.catch()
catch()
方法返回一个Promise
,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)
相同。事实上, calling
obj.catch(onRejected)
内部callsobj.then(undefined, onRejected)
。(这句话的意思是,我们显式使用obj.catch(onRejected)
,内部实际调用的是obj.then(undefined, onRejected)
)Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。上面代码中,
getJSON()
方法返回一个 Promise 对象,如果该对象状态变为resolved
,则会调用then()
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch()
方法指定的回调函数,处理这个错误。另外,then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获。◾ 下面是一个例子。
上面代码中,promise抛出一个错误,就被
catch()
方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。
◾ 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
◾ Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
上面代码中,一共有三个 Promise 对象:一个由
getJSON()
产生,两个由then()
产生。它们之中任何一个抛出的错误,都会被最后一个catch()
捕获。◾ 一般来说,不要在
then()
方法里面定义 Reject 状态的回调函数(即then
的第二个参数),总是使用catch
方法。上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面
then
方法执行中的错误,也更接近同步的写法(try/catch
)。因此,建议总是使用catch()
方法,而不使用then()
方法的第二个参数。◾ 跟传统的
try/catch
代码块不同的是,如果没有使用catch()
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。在浏览器中运行上面这段代码,等待两秒后,你会看到控制台正常打印"123",并没有因为
someAsyncThing()
方法里的错误阻塞代码运行。上面代码中,
someAsyncThing()
函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined
,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。处理错误
在上一章节的示例中,有个
catch
被附加到了 promise 链上。当 promise 链中的任何内容失败并引发错误或拒绝 promise 时,则控制权会转到链中最近的
catch()
语句。级联错误
如果在
catch()
内部引发错误,则可以附加第二个catch()
来处理,依此类推。Promise.prototype.finally()
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally()
方法返回一个Promise。在promise结束时,无论结果是fulfilled
或者是rejected
,都会执行指定的回调函数。这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在
then()
和catch()
中各写一次的情况。上面代码中,不管
promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。如果你想在
promise
执行完毕后无论其结果怎样都做一些处理或清理时,finally()
方法可能是有用的。◾ 由于无法知道
promise
的最终状态,所以finally
的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。◾ 与
Promise.resolve(2).then(() => {}, () => {})
(resolved的结果为undefined
)不同,Promise.resolve(2).finally(() => {})
resolved的结果为2
。◾ 同样,
Promise.reject(3).then(() => {}, () => {})
(fulfilled的结果为undefined
),Promise.reject(3).finally(() => {})
rejected 的结果为3
。并发 promise.all()
可以使用
Promise.all()
,发起多个并发请求,然后在所有 promise 都被解决后执行一些操作。ES2015 解构赋值语法也可以执行:
当然,不限于使用
axios
,任何 promise 都可以以这种方式使用,比如:竞速 promise.race()
race的用法是:传入多个promise实例,谁跑的快,就以谁的结果执行回调
Promise.race赛跑机制,只认第一名
传给
race()
的promise列表
,只要有一个promise被解决,则Promise.race()
开始运行,并且只运行一次附加的回调(传入第一个被解决的promise
的结果)。示例:
◾ 使用场景
reject
,否则变为resolve
实例:
◾ 把异步操作和定时器放到一起,如果定时器先触发,认为超时,告知用户:
现代浏览器原生支持fetch,所以我们可以直接在浏览器上运行上面的代码:
为了演示效果,这里
setTimeout
时间设小一点,可以看到定时器先完成,然后race()
方法以定时器的结果执行了回调把
setTimeout
设大一点,这次接口先请求完成,所以race()
以接口的结果执行了回调◾ 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为
reject
,否则变为resolve
。上面代码中,如果 5 秒之内
fetch
方法无法返回结果,变量p
的状态就会变为rejected
,从而触发catch
方法指定的回调函数。Promise.allSettled()
该
Promise.allSettled()
方法返回一个在所有给定的promise都已经fulfilled
或rejected
后的promise,并带有一个对象数组,每个对象表示对应的promise结果。当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
相比之下,
Promise.all()
更适合彼此相互依赖或者在其中任何一个reject
时立即结束。Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。该方法由 ES2020 引入。上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
该方法返回的新的 Promise 实例,一旦结束,状态总是
fulfilled
,不会变成rejected
。状态变成fulfilled
后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()
的 Promise 实例。上面代码中,
Promise.allSettled()
的返回值allSettledPromise
,状态只可能变成fulfilled
。它的监听函数接收到的参数是数组results
。该数组的每个成员都是一个对象,对应传入Promise.allSettled()
的两个 Promise 实例。每个对象都有status
属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。◾ 下面是返回值用法的例子。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,
Promise.allSettled()
方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。Promise.all()
方法无法做到这一点。上面代码中,
Promise.all()
无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了Promise.allSettled()
,这就很容易了。常见的错误
◾
Uncaught TypeError: undefined is not a promise
如果在控制台中收到
Uncaught TypeError: undefined is not a promise
错误,则请确保使用new Promise()
而不是Promise()
。◾
UnhandledPromiseRejectionWarning
这意味着调用的 promise 被拒绝,但是没有用于处理错误的 catch。 在 then 之后添加 catch 则可以正确地处理。
参考
查看原文
查看全部文章
博文系列目录
交流
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。