目录
then (Promise.prototype.then)方法
Promise.prototype.finally() 方法
回顾:
同步回调和异步回调
同步的回调函数是立即在主线程上执行不会放入回调队列中;异步的回调不会立即执行,会放入回调队列中以后执行。
错误处理
错误类型
- Error:所有错误的父类型
- ReferenceError:引用的变量不存在
- TypeError:数据类型错误
- SyntaxError:语法错误
错误处理
捕获错误:可以用try{ ..}.catch(){...}
try里面放可能出错的代码,一旦出错立即停止try中的代码执行,并调用catch携带错误信息
抛出错误:throw error
throw new Error("错误")
错误对象
message属性:错误的相关信息
stack属性:记录信息(错误的代码的位置等等)
promise
- 从语法上说promise是一个构造函数(内置);从功能上说promise的实例对象可以用来封装一个异步操作,并可以获取成功/失败的值。
- new promise 时,要传入一个函数(回调函数),它是同步的回调,会立即在主线程是执行,它被称为executor函数(执行器函数)
- 每一个promise 实例都有三种状态,分别是:初始化(pending)、成功(fulfilled)、失败(rejected)
- 每一个promise实例在刚被new出来的时候,状态都是 初始化(pending)。
- executor函数会接收两个参数。它们都是函数,分别用形参 resolve、reject 接收。
- 调用 resolve 会让promise实例的状态变为 成功(fulfilled) ,同时可以指定成功的value(值)
- 调用 reject 会让promise实例的状态变为 失败(rejected) ,同时可以指定失败的reason(原因)
const p = new Promise((resolve),(reject) => {
resolve('ok'),
reject('no')
})
// p接收实例化的Promise
// news实例化Promise
// resolve 成功
// reject 失败
基本编码流程
- 创建Promise的实例对象(初始化(pending)状态),传入 executor 函数
- 在 executor 中启动异步任务(定时器、ajax请求)
- 根据异步任务的结果,做不同的处理:
- 异步任务成功:调用resolve(value),让Promise实例对象状态变为成功(fulfilled),同时指定成功的value值。
- 异步任务失败:调用reject(reason),让Promise实例对象状态变为失败(rejected),同时指定失败的reason原因。
- 通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason(注意:then方法所指的的成功的回调和失败的回调都是异步的回调。
状态
三个状态:
- 初始化(pending):未确定的初始状态
- 成功(fulfilled) :调用esolve(value) 之后状态
- 失败(rejected) : 调用reject(reason)之后状态
状态的改变:只有两种
pending——fulfilled
pending——rejected
要注意的是状态只能改变一次。
一个promise指定多个成功/失败的回调函数,都会放入队列依次执行。(依次调用成功或者失败的回调函数)
promise构造函数
new promise (executor){ }
executor函数是同步执行的,(resolve),(reject) => { }
- 调用 resolve 会让promise实例的状态变为 成功(fulfilled) ,同时可以指定成功的value(值)
- 调用 reject 会让promise实例的状态变为 失败(rejected) ,同时可以指定失败的reason(原因)
executor 函数会在 promise 内部立即调用,异步代码放在 executor 函数中。
then (Promise.prototype.then)方法
Promise.prototype.then方法,Promise实例.then(onFulfilled,onRejected);
onFulfilled :成功的回调函数(value)=>{}
onRejected:失败的回调函数(reason)=>{}
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。 then方法会返回一个新的promise实例对象。
then 方法的特点:
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用,但通过 .then 形式添加的回调函数,不论什么时候,都会被调用。可以多次调用.then,(可以添加多个回调函数)它们会按照插入顺序并且独立运行。
then的链式调用我会在下面详细解释。
Promise.prototype.catch()方法
Promise.prototype.catch()方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。(它是then方法的语法糖)
异步操作抛出错误,状态就会变为rejected
,就会调用catch()
方法指定的回调函数,处理这个错误,另外,then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
// 例如
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
reject()
方法的作用,等同于抛出错误。
/ 写法一
const promise = new Promise(function(resolve, reject) {
try {
// throw 抛出错误
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
// reject() 抛出错误
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
如果Promise 状态已经变成resolved
,再抛出错误是无效的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
catch()
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()
方法。如果没有报错,则会跳过catch()
方法。
由于catch()
方法之中,还能再抛出错误。 所以还能调用第二个catch()
方法用来捕获前一个catch()
方法抛出的错误。
Promise.prototype.finally() 方法
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
// 不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
// 例如:服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally
方法里面的操作,是与状态无关的,不依赖于 Promise 的执行结果。
finally
方法总是会返回原来的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
Promise.all() 方法
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all(PromiseArr)
PromiseArr包含n个Promise实例的数组,会返回一个新的Promise实例,只有所以的Promise成功才成功,只要有一个失败就返回失败。
Promise.race()方法
Promise.race(PromiseArr)
PromiseArr包含n个Promise实例的数组,会返回一个新的Promise实例,但是成功还是失败取决于最先出结果的promise。
Promise.allSettled()方法
Promise.allSettled()
方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。
只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled
还是rejected
),返回的 Promise 对象才会发生状态变更。
Promise.any() 方法
Promise.any() 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
Promise.resolve() 方法
Promise.resolve(value) 用于快速返回一个状态为fulfilled或者rejected的promise实例对象。
value的值可能是:
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve
将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable
对象
thenable
对象指的是具有then
方法的对象。Promise.resolve()
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then()
方法。
(3)参数不是具有then()
方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()
方法的对象,则Promise.resolve()
方法返回一个新的 Promise 对象,状态为resolved
。
(4)不带有任何参数
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()
方法。
要注意的是,立即resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
比如: 有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就可以做到。
Promise.reject() 方法
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。
可以用于快速返回一个状态为rejected的Promise实例对象。
const p0 = Promise.reject(100)
const p = Promise.resolve(p0)
p.then({
value=>{console.log('成功了',value);}
reason=>{console.log('失败了',reason);
})
// 状态为失败,因为p的值是失败的promise。
改变promise实例状态
-
执行 resolve(value):如果当前是pending就会变成fulfilled
-
执行 reject(reason):如果当前是pending就会变成rejected
-
执行器函数(executor)抛出异常:如果当前是pending就会变成rejected
改变promise实例的状态和指定回调函数谁先谁后呢?
- 都有可能,正常情况下先指定回调在改变状态,但也可以先改状态再指定回调。
- 先改状态再指定回调可以先延迟一会儿再调用then
- promise实例什么时候才得到数据呢? 如果先指定的回调,那么当状态发生改变时,回调函数就会调用得到数据;如果先改变的状态,那么当指定回调时,回调函数就会调用得到数据。
then的链式调用
promise实例的then()返回的是一个新的promise实例,它的值和状态由:
- 由then() 所指定的回调函数执行的结果决定。
- 如果then所指定的回调函数返回一个非promise的值 a ,则新的promise实例状态为成功(fulfilled),value为 a 。
- 如果then所指定的回调函数返回一个promise的值 p ,则新的promise实例状态和值与 p 一致
- 如果then所指定的回调函数抛出异常,如(throw),则新的promise实例状态为rejected,reason为抛出的异常。
promise串联多个异步任务也是通过then的链式调用。
中断链式调用
当使用promise的then 链式调用时,在中间中断,不再调用后面的回调函数,可以在失败的回调函数中返回一个padding状态的promise实例。
// reason 中返回状态为padding的Promise。 这样失败后就不会继续调用下去了。
return new Promise(()=>{})
promise错误穿透
当使用promise的then链式调用的时候,可以在最后用catch指定一个失败的回调。当前面任何操作出现错误都会传到最后失败的回调中处理了。
如果不存在then的链式调用那么就不需要使用then的错误穿透
promise优势
1.指定回调函数的方式更加灵活
旧的方法需要在启动异步任务之前指定回调
promise在启动异步任务=>返会promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
2.支持链式调用,可以解决回调地狱的问题。
回调地狱:回调函数嵌套调用外部回调函数,异步执行的结果是嵌套的回调函数执行的条件。(层层嵌套的回调函数就是回调地狱)
回调地狱缺点是代码不便于阅读,也不便于异常的处理。
解决回调地狱的方式:then的链式调用和async/await (它底层其实还是使用的then的链式调用)
了解一下宏队列和微队列
宏队列:[宏任务1,宏任务2.....]
微队列:[微任务1,微任务2.....]
规则:每次要执行宏队列里的任务之前,先看微队列里是否有待执行的微任务。如果有,先执行微任务,如果没有,则按照宏队列里的任务顺序依次执行。
定时器所指定的回调属于宏任务,promise里的回调属于微任务,微队列的优先级比宏队列的优先级要高。
要注意的是只有promise里的回调是属于微任务。
小结
Promise 是现代 JavaScript 异步编程的基础。它避免了深度嵌套回调,使表达和理解异步操作序列变得更加容易,并且它们还支持一种类似于同步编程中 try...catch
语句的错误处理方式。
Promise 在所有现代浏览器的最新版本中都可以使用;唯一会出现支持问题的地方是 Opera Mini 和 IE11 及更早的版本。
MDN:如何使用 Promise - 学习 Web 开发 | MDN
学习参考:promise对象
以及学习:Promise - 廖雪峰的官方网站