Promise 使用详解
上篇讲了 Proxy + Reflect 即对象的代理和统一的对象处理方法, 然后对应的案例就是 vue 的响应式原理实现. 从响应式认识, 依赖函数收集, 依赖函数更新, 监听对象的变化, 设计对象与对象, 对象与属性的嵌套 map 数据结构, 到正确收集依赖, 全局的 globalActiveReactiveFn 变量, 重构 Deep 类, 封装通用响应式函数等过程, 一步步从零实现响应式原理, 这还是值得仔细回味的.
那本篇, 则会开始学习 es6 中新增的 Promise 类, 会分为两部分, 基本使用和手写 Promise. 也算是 js 中的一个必须要彻底掌握的大知识点吧, 就真的了解一个东西, 一定要尽可能从原理出发, 后续 curd 才能更得心应手呢.
异步任务处理
我自己一直对 js 中的回调函数很不理解, 包括函数作为参数, 回调函数执行时机, 异步编程啥的都是一知半解. js 又是一个单线程语言, 没有什么多线程的说法, 那这种异步任务实现, 设计的任务队列啥的, 搞得头晕. 然后这个 Promise 其实也是基于回调函数的, 就更加迷惑了.
所以还是得从一些实际案例开始搞起.
- 调用一个函数, 在这函数中发送网络请求 (定时器模拟)
- 请求成功, 则告诉调用者成功, 并将成功数据返回
- 请求失败, 则告诉调用者失败, 并将失败信息返回
// 异步任务-网络请求
// request.js
function requestData(url) {
// 模拟网络请求
setTimeout(() => {
if (url == 'youge') {
// 成功
let data = [1, 2, 3]
// 这个 return 是返回给 setTimeout 而非 requestData!
console.log(data)
return data
} else {
// 失败
let err = "请求失败"
console.log(err)
return err
}
}, 2000);
}
// main.js
// 对于调用者来说, 不论成功失败, 在 2s 后必须给我一个结果
// 直接 return 是不行的哦
const res = requestData("youge")
console.log(res) // undefined
// 等待 2秒后, 控制台输出
// [1, 2, 3]
这里的 res 是拿不到结果的:
- setTimeout 只是
注册
了一个未来 2秒后要执行的任务 - requestData 函数在调用 setTimeout 后 立即结束, 返回 undefined
- 2 秒后, setTimeout 的回调函数在 另一个宏任务中继续执行, 但此时的 requestData 已结束, 返回确定值 undefined.
可以看到, 在 打印 undefined 之后, 程序并未结束, 它只是同步的部分执行完了, 但 js 运行环境还在的, 要等 2秒后异步的宏任务也执行完了, 才会真正结束.
这里要简单补充一下 js 的 事件循环
机制: 即 js 引擎 (主线程) 有一个永不停止的循环, 它的大致流程:
- 执行当前同步代码 (当前宏任务)
- 再检查执行微队列任务, 执行完后就清空它
- 再检查执行 宏队列任务 , 取出第一个任务, 并执行
- 重复上述步骤
因此同步函数只会获取到 undefined. 那以前要获取异步函数的结果, 则是通过传入一个 回调函数 callback
,
// 通过回调函数, 来获取 异步任务的结果
// request.js
function requestData(url, successCallback, errorCallback) {
// 模拟网络请求
setTimeout(() => {
if (url == 'youge') {
// 成功
let data = [1, 2, 3]
successCallback(data)
} else {
// 失败
let err = "请求失败"
errorCallback(err)
}
}, 2000);
}
// main.js
// js 支持函数式编程, 因此函数也可以作为参数, 变量等
// 调用时, 实参传入一个函数, 对应 successCallback(data)
requestData(
'youge1',
(res) => console.log(res),
(err) => console.log(err)
)
// 等待了2秒后, 打印出了 [ 1, 2, 3 ]
// 将'youge' 改为 'cj' 等待2秒后, 打印 "请求失败"
为什么回调函数就能获取到异步数据呢? 核心机制就是实现了 闭包!
仔细来分析这里的关键代码, 看看这个闭包是如何形成的.
function requestData(url, successCallback, errorCallback) {
setTimeout(
// 1. 此箭头函数, 是 setTimeout 的回调, 并定义在 requestData 内部
() => {
// 2.此内部函数, 引用了父函数 requestData 的 url, successCallback 等自由变量
if (url == 'youge') {
let data = [1, 2, 3]
successCallback(data)
} else {
let err = "请求失败"
errorCallback(err)
}
// 3.此 setTimeout 将 内函数 "带走" 给了浏览器, 产生 "逃逸" 现象
}, 2000);
}
注意这里的 setTimeout 的作用仅是通知 "浏览器" 2秒后执行函数,
之前分析闭包是通过, 函数嵌套的, 内函数引用外部变量, 返回内函数.
// 经典闭包方式
function outer(name, age) {
function inner() {
console.log(name, age)
}
return inner
}
inner = outer('cj', 18)
inner() // cj, 18
上面的请求函数, 未 return 内函数的方式形成闭包, 而是通过setTimeout
产生了内函数的逃逸,形成闭包!
闭包形成条件 | 说明 |
1. 函数嵌套 | 内部函数在外部函数内定义。 |
2. 引用外部变量 | 内部函数使用了外部作用域的变量, js 词法作用域父级查找机制 |
3. 内部函数“逃逸” | 外部函数结束后,内部函数仍可被调用(通过返回、异步、事件等)。 |
这个 "逃逸"
的意思就是, 内函数的生命周期, 超出 了其定义的词法作用域, 定义时的执行上下的生命周期.
对于上面这个请求函数来说:
- 在 外函数 requestData() 中, 又嵌套了一个 内函数 (箭头函数), 满足闭包条件 1
- 在内函数中, 访问了外函数的 自由变量, url, successCallback 等, 满足闭包条件 2
- 在 setTimeout 的机制下, 内函数被 "传送" 到了浏览器, 并未随着而外函数执行完而销毁, 因此出现了 逃逸现象
因此, 这个过程完美实现了 闭包
, 因而在外函数 requestData 执行完毕后, 回调函数依然能获取到的数据啦, 核心就是理解闭包!
定义时:闭包“形成”
- 当箭头函数被定义,并引用了
url
等变量时,js 引擎就“标记”了这个函数需要一个闭包
逃逸时:闭包“激活”
- 当
requestData
执行完毕,其栈帧即将销毁时,引擎发现“有个逃逸的函数还需要这些变量” - 于是,闭包被真正“激活”:这些变量从栈内存转移到堆内存,被闭包结构持有,不会被垃圾回收
执行时:闭包“生效”
- 2秒后,浏览器执行该函数,它通过闭包访问到堆中的变量,顺利完成任务
回调函数的弊端
- 若为自己封装的 requestData, 则需自行设计好 callback 等名称, 容易随心所欲
- 若用的是别人封装的 requestData, 则需去看别人的源码或者文档, 容易五花八门
由此可见回调函数的弊端在于它没有统一的规范性, 要么堆自己的屎山, 要么扒别人的黑盒, 还容易造成 回调地狱
.
因此更好的方案是约定一个 Promise, 译为 "承诺" 或者 "期约", 它在某个阶段一定会返回一个结果, 不管成功还是失败, 而且是通用的, 并且已规范好所有的代码逻辑, "约定大于开发".
// 更好的方案: 承诺
function requestData2() {
return '承诺'
}
const promise = requestData2()
Promise 介绍
Promise 是一个类, 可以翻译为, 承诺, 许诺, 期约等, 但建议别翻, 就叫 Promise 即可.
当我们需要给调用者一个承诺: 待会儿我会给你回调函数的时候, 就可以创建一个 Promise 对象.
在通过 new 创建 Promise 对象 时, 需要传入一个回调函数, 称为 executor
- 此回调函数会被立即执行, 并且传入另外两个回调函数,
resole
,reject
都是形参, 名字可该但不建议 - 当调用 resovle() 时, 会执行 Promise 对象 的
then()
方法传入的回调函数
- 当调用 reject() 时, 会执行 Promise 对象的
catch()
方法传入的回调函数
这就是一个约定的开发规范, 先创建一个对象, 然后给它传两个回调函数, 然后在不同的时刻调这俩回调函数, 同时又去调用传入的回调函数 ... 看上去绕来绕去的, 但其实就是对一对回调函数进行有序管理而已.
// promise 使用
// 传入的回调函数, 称为 executor
// resolve, 形参, 回调函数, 在成功时调用
// reject, 形参, 回调函数, 在失败时调用
const promise = new Promise((resolve, reject) => {
// 当成功的时候, 就调用 resolve
// resolve('成功')
// 失败就调用 reject
reject('失败')
})
// 外部要接收这个成功, 则调用 promise.then() 方法
// 再传入一个回调去获取成功的结果
promise.then((res) => {
console.log(res) // 成功
})
// 如果内部失败了, 则调用 promise.catch() 方法
// 再传入一个回调函数去获取失败的结果
// 当然这样写是错的哈, 只是逻辑演示
promise.catch(err => {
console.log(err)
})
这个 promise 对象创建过程, 要是不好理解可以用咱们之前 class 的语法来模拟一下:
// 模拟 初始化 promise
class Person {
constructor(callback) {
let foo = () => console.log('inner foo')
let bar = () => console.log('inner bar')
callback(foo, bar) // 立即调用
}
}
const p = new Person((foo, bar) => {
foo() // inner foo
bar() // inner bar
})
可以看到 Promise 就是一个约定机制,
当成功的时候, 就调用 resolve, 对应的外部通过 then() 传入一个回调函数去获取结果.
当失败的时候, 就调用 reject , 对应的外部通过 catch() 传入一个回调函数去获取结果.
还能进行 "链式嵌套", 即当获取的结果, 又是一个 Promise, 不就形成了链式关系了嘛, 总比层层回调嵌套强吧.
Promise 重构请求
了解了 Promise 的基本使用后, 就可以对之前的 requestData() 进行重构了.
// 重构 requestData(), 用上 Promise
// request.js
function requestData(url) {
// 返回一个 Promise, 将异步代码放 executor 中即可
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url === 'youge') {
let data = [1, 2, 3]
// resolve 告诉外面成功啦
resolve(data)
} else {
let err = '请求失败'
// reject 告诉外面失败了
reject(err)
}
}, 2000);
})
}
// main.js
const promise = requestData("youge")
// then() 也可以传入2个回调函数
// 第一个回调: 在 resovle() 时被调用
// 第二个回调: 在 reject() 时被调用
promise.then(
(res) => console.log(res),
(err) => console.log(err)
)
封装过程, 就是将这个异步任务放到 Promise 的 executor 中去, 根据任务执行的成功或者失败状态, 来控制调用 resovle 或者 reject. 然后外部的而调用者, 就可以根据内部的 "信号" 来进行后续逻辑处理.
// then, catch 更推荐这样写
promise.then(res => {
console.log(res) // [ 1, 2, 3 ]
}).catch(err => {
console.log(err)
})
无论是 .then(onFulfilled, onRejected)
还是 .then().catch()
,它们都能处理原始 Promise 被 reject 的情况, 更推荐后者, 因为一旦涉及链式调用就麻烦了.
从代码角度看, 好像重构之后反而变复杂了, 但其实是变更简单了. 因为它遵循了一种规范, 这个规范告诉我们, 后续任何一个第三方库, 只要它说会返回一个 Promise , 那我就秒懂, 赶紧搞上 then() 和 catch() 来等待结果返回即可. 这样反而省去了大量的沟通成本. 有点那种 "面向接口编程"的感觉.
// 事先知道会返回 Promise, 那直接一把梭干完
const promise = 管你是啥()
promise.then((res) => {}).catch((err) => {})
Promise 三种状态
分析 Promise 的使用过程, 从一开始的执行 executor 中的代码, 然后到 resolve() 或者 reject() 函数的调用, 然后对应到外部去获取结果的 then().catch() 过程, 可以将它分为 3 个状态:
-
pending
: 初始待定状态, 既没有兑现, 也没有拒绝, 悬而未决 -
fulfilled
: 已兑现, 意味着操作成功, 当执行 resovle() 时处于该状态 -
rejected
: 已拒绝, 意味着操作失败, 当执行 reject() 时处于该状态
// Promise 状态
// Promise 的状态一定确定, 则不可修改
new Promise((resovle, reject) => {
console.log('execotor call ...') // pending 状态
resovle('ok') // 会跳到 then 的第一个回调
reject('err') // 会跳到 then 的第二个回调, 这里无意义, 因为已调 resovle
}).then(
res => console.log("res: ", res), // fulfilled 状态
err => console.log("err: ", err) // rejected 状态
)
注意, Promise 状态一定确定, 则不可更改. 比如上面我们, 先是调用 resolve(), 然后再调用 reject() 是不生效的. 因为在 调用 resovle() 的时候, 状态就已经确定为 fulfilled 了, 不会再更改.
// Promise 的状态一定确定, 则不可修改
reject()
rerole() // 它也是不生效的, 因为状态已经被 reject 确定了
resovle 传不同类型参数的区别
先说结论, 通常就 3 种情况:
- 传入普通值或者对象, 则会被作为
then
回调函数的参数 - 传入 一个另外的Promise, 则新 Promise 会决定原 Promise 的状态
- 传入 一个实现了 then 方法的对象, 则根据 then 方法的结果来决定 Promise 的状态
// rosovle: 普通值或者对象
new Promise((resovle, reject) => {
resovle({name: 'youge', age: 18})
// res: { name: 'youge', age: 18 }
}).then(res => console.log("res: ", res))
然后是传新的 Promise 情况:
// rosovle: 新的一个 promise
// 当前的 Promise 状态会由传入的 Promise 来决定
const newPromise = new Promise((resovle, reject) => {
// 当前状态: pending
})
new Promise((resovle, reject) => {
resovle(newPromise) // 状态由 newPromise 决定
}).then(res => console.log("res: ", res)) // 啥也没有
会发现此时什么也没有发生.因为 resolve 传入的是一个 新的 Promise, 则当前状态会发生移交给 这个 新的 Promise, 而此时它的状态还是处于 pending , 啥也没干嘛, 因此什么也不会打印出来.
const newPromise = new Promise((resovle, reject) => {
// 状态锁定 fulfilled
resovle("new promise fulfilled")
})
new Promise((resovle, reject) => {
resovle(newPromise)
// res: new promise fulfilled
}).then(res => console.log("res: ", res))
最后看传入的是一个实现了 then 方法 (thenable 接口) 的对象, 则状态由 then()
来决定.
// rosovle: 实现了 then 方法的对象
// 当前的 Promise 状态会由 then() 来决定
const obj = {
name: 'youge',
then: function(resovle, reject) {
resovle('resovle msg')
}
}
new Promise((resovle, reject) => {
resovle(obj)
// rres: resovle msg
}).then(res => console.log("res: ", res))
Promise 对象方法
// Promise 对象方法, 都是放原型上的
console.log(Object.getOwnPropertyDescriptors(Promise.prototype))
{
constructor: {
value: [Function: Promise],
writable: true,
enumerable: false,
configurable: true
},
then: {
value: [Function: then],
writable: true,
enumerable: false,
configurable: true
},
catch: {
value: [Function: catch],
writable: true,
enumerable: false,
configurable: true
},
finally: {
value: [Function: finally],
writable: true,
enumerable: false,
configurable: true
},
[Symbol(Symbol.toStringTag)]: {
value: 'Promise',
writable: false,
enumerable: false,
configurable: true
}
}
都是几个会高频用的对象方法, then(), catch(), finally() 等.
then方法- 多次平行调用
then 方法是 Promise 对象上的一个方法 , 接收两个参数, 并返回一个 Promise.
在同一个 Promise 中, 可以多次平行调用 then 方法的. 应用场景可以体现在:
- 多个模块, 组件, 需要传入同一份数据
- 分离关注点, 智能解耦, 如获取数据后, 同步进行 UI 更新, 处理日志, 处理缓存等
- 减少重复异步操作, 一次源头, 多出消费等
// 1. 同一个 Promise 可以被多次平行调用 then 方法
// 当 resolve 被回调时, 所有的 then 方法都会被调用
// 一次获取, 多处消费
promise.then(res => {
console.log("res1: ", res) // 更新UI
})
promise.then(res => {
console.log("res2: ", res) // 更新日期
})
promise.then(res => {
console.log("res3: ", res) // 清理缓存
})
res1: 111
res2: 111
res3: 111
then 方法返回 Promise
就 then(onFulfilled, onRejected) 的返回值是一个新的 Promise . 它的状态由 传入的回调函数返回值决定
01: 当 onFulfilled, 回调函数, 返回一个普通值/对象时, 新 Promise 自动以 return 的值作为 resovle 的值
// then() 的返回值是一个新的 Promise
// then 方法的回调返回 普通值 (数值, 字符, 对象等)
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then(res => {
return 222 // 会作为 then返回的新 Promise 的 resovle 值
})
这里的 "return 222" 背后做了类似这样的事情:
// then() 的返回值是一个新的 Promise
promise.then(res => {
// return 222
return new Promise((resolve) => {
resolve(222)
})
})
既然 then() 返回了一个新的 Promise, 且里面的回调函数返回了普通值, 会作为 新 Promise 的 resovle 这.
那这样就又可以调用 then 方法了.
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then(res => {
return 222
// then 链式调用, 本质上市 then 返回了一个 新 Promise
}).then(res => {
console.log(res) // 222
})
理解的关键点是 这里的第二个 then 对应的是第一个then 返回的 新 Promise.
// 这一整块, 就是一个新的 Promise
promise.then(res => {
return 222
})
按照这个机制, then 方法又会防止一个 promise, 只要 resovle 值 ok, 不就能一直 链式调用下去了呀.
// then 链式调用
promise.then(res => {
console.log(res) // 111
return 222
}).then(res => {
console.log(res) // 222
return 333
}).then(res => {
console.log(res) // 333
return 444
}).then(res => {
console.log(res) // 444
// return
})
就是因为 每 return 普通值, 一次就返回 新 Promise, 普通值自动带过去作为 resovle 值, 又可以继续 then() 下去.
当然默认 return 是 undefined , 也不会影响 then 的链式调用的.
02: 当 then 的回调返回的是一个 Promise, 则返回的 新 Promise 会等待并继承(内) 返回的 Promise 的结果
// then 方法的回调返回 Promise
// Promise 链式解析
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then(res => {
// 上层要等它的状态决定
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(222)
}, 2000);
})
}).then(res => {
// then 返回的 Promise 等待, then 内部回调的 promise 的状态
console.log("res: ", res) // 3秒后, 222
})
then 返回的 新 Promise 会等待, then 内部回调中, 返回的 Promise 的状态而决定状态.
03: 当 then 的回调返回是一个实现了 thenable 的对象, 则返回的 新 Promise 会等待其完成
// then 方法的回调返回 thenable
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then(res => {
return {
name: 'youge',
then: (resolve, reject) => {
resolve(222)
}
}
}).then(res => {
// js会识别 thenable 并将其运行, 新 Promise 会等待它完成
console.log("res: ", res) // 222
})
04: 当 then 的回调中 throw 错误, 则返回的 新 Promise 被 reject, 错误被传递
// then 方法的回调 throw
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then(res => {
throw new Error("出错了");
}).catch(err => {
console.log("err: ", err) // 222
})
then 回调函数返回值类型 |
|
普通值(数字、字符串等) | 自动 resolve 为该值 |
| 等待该 Promise,结果传递下去 |
| 展开并等待它 |
抛出异常 | reject,错误被捕获 |
| resolve 为 |
关键还是理解Promise 实现异步链式调用的原理和过程, 再来举一个例子:
// 模拟获取用户数据的过程
fetch('/api/user')
.then(res => res.json()) // 返回 Promise
.then(user => fetch(`/api/posts?uid=${user.id}`)) // 返回新 Promise
.then(res => res.json()) // 返回新 Promise
.then(posts => console.log(posts)) // 返回新 Promise
.catch(err => console.error(err));
每一步 .then()
都返回一个新 Promise,且能“等待”上一步返回的异步结果,形成流畅的链式调用.
catch方法
这个 .catch(onRejected)
本质上是 .then(null, onRejected)
的语法糖.
.cath()
的返回值和 then() 一样, 也是一个 Promise, 除非异常, 否则还是传 resolve 的
promise.catch(err => {/* 处理错误 */})
// 等价于
promise.then(null, err => {/* 处理错误 */})
这个 es6 实现的 catch 还是很方便去帮助捕获异常的.
// catch 方法 对应 then 方法的第二个回调函数
const promise = new Promise((resolve, reject) => {
reject("reject-1")
// throw new Error("reject-1");
})
// 1. 当 executor 抛出异常, 也会被 catch 捕获到
promise.then(null, err => {
console.log(err) // reject-1; Error: reject-1
})
// 语法糖写法, 通过 catch 方法来传入错误
// 但 es6 的这种写法不符合 promise A+ 规范, 如何呢, 又能怎
promise.catch(err => {
console.log("err: ", err) // err: Error: reject-1
})
promise.then(res => {
// return 111
return new Promise((resolve, reject) => {
reject(222)
})
}).catch(err => {
// 这里的 catch 对应的还是原来的 Promise 哈
console.log('old promise: ', err)
})
当 reejct 之后, 必须要处理异常, 不然会有问题的.
// reject 之后, 必须要处理异常
const promise = new Promise((resolve, reject) => {
reject(111);
});
// 相当于独立调用, 会有问题, 因为 reject 没有被处理
promise.then(res => console.log('success:', res));
promise.catch(err => console.log('error:', err));
改成这样就好了:
// 正确处理异常
promise.then(
res => console.log('success:', res)
).catch(err => console.log(err))
catch() 的返回值和 then() 是一样的, 都是返回一个新的 promise, 若不是传异常, 则都是传给 resolve
const promise = new Promise((resolve, reject) => {
reject(111);
});
promise.then(
res => console.log('success:', res)
).catch(err => {
console.log(err)
return 'err1'
}).then(res => {
console.log(res) // 走的是这里
}).catch(err => {
console.log('err1: ', err) // 没有走
return new Promise((resolve, reject) => {
reject("err2")
})
}).then(res => {
console.log("res3: " ,res) // 走这里 undefined
}).catch(err => {
console.log("err3: ", err) // 没有走
})
就只要一步步去分析, 状态的变更 , 对应的哪些执行, 哪些不执行, 慢慢还是能分析处理的.
最后和 then 方法做一个对比吧 (问 ai 的哈):
catch 回调函数返回值类型 |
|
普通值(如字符串、数字) | 新 Promise resolve 为该值 ✅ |
| 等待该 Promise,结果传递下去 ✅ |
| 展开并等待 ✅ |
抛出异常 | 新 Promise 被 reject ❌ |
无返回( | resolve 为 |
这和 .then()
的规则 完全一致!继续来对比 .then() 和 .catch():
特性 |
|
|
用途 | 处理成功和失败(但不推荐第二个参数) | 专门处理错误 ✅ 推荐 |
是否返回新 Promise | ✅ 是 | ✅ 是 |
能否链式调用 | ✅ | ✅ |
回调返回值如何处理 | 自动 resolve / 等待 Promise | 完全相同 ✅ |
能否恢复 Promise 链? | ✅(在 onRejected 中返回值) | ✅(返回值可恢复) |
是否捕获前面的错误? | 第二个参数只捕获原始 reject | ✅ 捕获链中任意 reject 或异常 |
可读性 | ❌ 差(不推荐用于错误处理) | ✅✅✅ 好 |
.catch()
的本质:
项目 | 说明 |
✅ 是什么 |
|
✅ 返回值 | 一个新 Promise,行为由回调返回值决定 |
✅ 能做什么 | 捕获错误、恢复链、返回值、抛出新错误 |
✅ 为什么好用 | 语法清晰、可链式、能统一处理错误 |
✅ 核心机制 | 和 |
finally 方法
它是在 es9 中新增的特性, 表示不论 Promise 对象变成 fulfilled 还是 rejected 状态, 最终都会执行的代码. 就类似
try-catch-finally
的方式. 它也是接收一个回调函数, 但不接收参数, 因为都会执行嘛.
它也不是 promise A+ 规范的要求, 是 es 帮我们实现的, 这样开发体验会更好.
// finally 方法
const promise = new Promise((resolve, reject) => {
//resolve('resovle 111')
reject("reject 222")
})
promise.then(res => {
console.log("res: ", res)
}).catch(err => {
console.log("err", err)
}).finally(() => {
console.log('不管啥状态, finally 都会执行')
})
Promise 类方法
上面讲的 then, catch, finally 等方法都是属于 Promise 的对象方法, 或者实例方法, 是很在 Promise 类 或者 称为构造函数的显示原型上的 , 即 Promise.prototype. 这里再来补充几个静态方法, 或者称为 类的方法, 即直接通过 Promise.xxx() 调用的方法.
resolve() 方法
有时我们已经有现成的数据内容, 希望将其转为 Promise 来给别人使用, 则可通过 Promise.resolve()
来实现, 相当于 new Promise 并执行了 resovle 操作.
// resovle 类方法-普通值
// 将数据转为 Promise 给外部调用
function foo() {
const obj = { name: 'youge', age: 18 }
// 将 obj 以 Promise 方式返回
return new Promise((resolve, reject) => {
resolve(obj)
})
}
// 外部通过 then 来获取
foo().then(res => console.log(res))
// 也可以直接用 Promise.resolve() 简化
const promise = Promise.resolve({name: 'youge', age: 18})
// { name: 'youge', age: 18 }
promise.then(res => console.log(res))
和原来一样, 如果是传入的不是普通值, 是 Promise , 则状态也是由, 传入的 Promise 而定的.
// resovle 类方法-传入 Promise
const promise = Promise.resolve(
new Promise((resolve, reject) => {
// 状态由这里决定
// resolve(123)
reject('err')
})
)
promise.then(res => {
console.log(res) // 123
}).catch(err => {
console.log(err) // err
})
reject 静态方法
和 resovle 一样的, 也是自动帮我们完成 new Promise 然后进行 reject 的过程.
// reject 类方法 传入普通值
// const promise = Promise.reject("err")
// 相当于
// const p2 = new Promise((resolve, reject) => {
// reject('err')
// })
// 注意: 和 resovle 不同, 它这里不论传入什么值, 都会走 catch
// const promise = Promise.reject(
// {
// then: function(resolve, reject) {
// resolve("okk")
// }
// }
// )
const promise = Promise.reject(
new Promise((resolve, reject) => {
// 即便这里是 resovle, 还是被 catch
resolve('okk')
})
)
promise.then(res => {
console.log("res: ", res)
}).catch(err => {
// err: { then: [Function: then] }
// err: Promise { 'okk' }
console.log("err: ", err) // err
})
注意: 和 resovle 不同, 它这里不论传入什么值, 都会走 catch
all 静态方法
它的作用是将多个 Promise "包裹" 为一个新的 最终 Promise, 然后输出一个最终的状态:
- 当
所有
Promise 状态为 fulfilled, 则返回的新 Promise 状态为 fulfilled, 各返回值组成一个数组 - 当
存在
Promise 状态为 rejected , 则返回的新 Promise 状态为 rejected, 并将收个 rejeted 返回值作为参数
这就很适合那种要等多个异步任务, 一并成功, 才能往下, 否则就不通过的场景呀.
// all 类方法
// 多个异步任务一并等待最终状态, 再来决定后续业务
// 要么一并成功, 但凡有一个不成功就视为全失败
const p1 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待2秒
resolve(111)
}, 2000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待1秒
//resolve(222)
reject(222)
}, 1000);
})
const p3 = new Promise((resolve, reject) => {
//resolve('okk') // 等待0秒
reject('okk')
})
// 需求: 等所有 Promise 都 fulfilled 时, 再拿结果
// 意外: 拿到最终结果前, 存在一个 Promise 为 rejected 则整个 Promise 状态 rejected
Promise.all([p1, p2, p3, 'aaa']).then(resArr => {
// 返回的是结果数组, 等待 2+1+0 = 3秒后
// 结果顺序是数组传入的顺序, 非返回结果排序
console.log(resArr)
}).catch(err => {
// 其中一个是 reject 则这个失败的就作为整体的 catch 结果
// 谁先返回失败, 就拿它当返回值
console.log(err) // 222
})
allSettled 静态方法
它是在 es11 中新增的, 对比 all 方法的区别在于:
- 它会等待所有的 Promise 都有结果时 (不论是 pending, fulfilled, rejected), 才会有最终结果
- 最终结果一定是 fulfilled
返回的是一个数组对象, 里面记录了每个传入的 Promise 的状态, 以及成功或者失败的结果, 很清晰.
// allSettled 类方法
const p1 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待2秒
resolve(111)
}, 2000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待1秒
//resolve(222)
reject(222)
}, 1000);
})
const p3 = new Promise((resolve, reject) => {
//resolve('okk') // 等待0秒
reject('okk')
})
// allSettled 会等待每个 Promise 都有结果再决定
Promise.allSettled([p1, p2, p3, 'aaa']).then(resArr => {
console.log(resArr)
}).catch(err => {
console.log(err )
})
// 结果直观一览
[
{ status: 'fulfilled', value: 111 },
{ status: 'rejected', reason: 222 },
{ status: 'rejected', reason: 'okk' },
{ status: 'fulfilled', value: 'aaa' }
]
race 静态方法
这个单词就是 "竞技" 的意思, 就咱说的赛马, 多个 Promise 竞争, 谁先有结果, 就用谁的结果
// race 类方法, 赛马
const p1 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待2秒
resolve(111)
}, 2000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待1秒
//resolve(222)
reject(222)
}, 1000);
})
const p3 = new Promise((resolve, reject) => {
//resolve('okk') // 等待0秒
reject('okk')
})
// race: 赛马, 谁先有结果, 就用谁的作为最终结果
Promise.race([p1, p2, p3, 'aaa']).then(resArr => {
console.log(resArr) // okk
}).catch(err => {
console.log(err )
})
自然是 p3 效率快, 一下子就返回结果了, 那就选它!
any 静态方法
它是 es12 中新增的, 和 race 方法有点类似, 但区别在于:
- any 方法会等到一个 fulfilled 状态, 才会决定新的 Promise 的状态 ( race 就只要返回就中)
- 若所有 Promise 都是 rejected, 则也会等待所有, 都变成 rejected 状态, 然后报一个 AggregateError 错误
// any 类方法
const p1 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待2秒
reject(111)
}, 2000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => { // 等待1秒
//resolve(222)
reject(222)
}, 1000);
})
const p3 = new Promise((resolve, reject) => {
reject('okk')
})
// any: 要等到一个 fulfilled 的保底, 而 race 来了就冲
// 都没等待, 就遗憾了
Promise.any([p1, p2, p3]).then(resArr => {
console.log(resArr) // okk
}).catch(err => {
console.log(err )
})
// 都失败了的话
[AggregateError: All promises were rejected] {
[errors]: [ 111, 222, 'okk' ]
}
至此, 关于 Promise 的基本使用部分就基本结束了, 其实就是一个官方推出的开发规范而已, 将这些一堆的回调函数都有序组织起来, 分不同的阶段进行调用而已. 关键是要理解异步编程和回调函数, 尤其是这个回调函数, 它的实现原理是闭包, 所以要了解闭包和作用域, 然后还要了解这个事件循环, 这一套下来, 其实这个 Promise 也不难. 那下篇就跟着网上的大佬来手写一边 Promise 试试吧.
耐心和恒心, 总会获得回报的.