Promise 的一些注意点,以及梳理
前言
昨天面试去 xiaoman, 提到了 promise;
面对不断深入发问, 我脑子一片空白, 然后就想到了白云山;
我想怎么思考其他问题的时候没有这么思维活跃;
常用方法
1 2 3 4 5 6 7 8 9 10
| Promise.prototype.then() Promise.prototype.catch() Promise.prototype.finally() Promise.all() Promise.race() Promise.allSettled() Promise.any() Promise.resolve() Promise.reject() Promise.try()
|
Promise 状态的不可逆性
Promise 状态的一旦变成 resolved 或 rejected 时,Promise 的状态和值就固定下来了,不论你后续再怎么调用 resolve 或 reject 方法,都不能改变它的状态和值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var p1 = new Promise(function (resolve, reject) { resolve('success1') resolve('success2') })
var p2 = new Promise(function (resolve, reject) { resolve('success') reject('reject') })
p1.then(function (value) { console.log(value) })
p2.then(function (value) { console.log(value) })
|
resolve 或 reject 后面的代码会不会执行
当然会
then 的第二个参数和 catch
catch
相当于then(null,function(err){ /*处理错误*/})
的写法
1 2 3 4 5 6 7 8 9 10 11 12
| new Promise((resolve) => { foo.bar() }).then( (res) => { console.log(res) }, (err) => { console.log(err) } )
|
1 2 3 4 5 6 7 8 9 10 11 12
| new Promise((resolve) => { foo.bar() }) .then((res) => { console.log(res) kk() }) .catch((err) => { console.log(err) })
|
上面代码中,catch
要好于then(null,err=>{})
写法, catch 可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try/catch)
。因此,建议总是使用catch
方法,而不使用then
方法的第二个参数。如下:
1 2 3 4 5 6 7 8 9 10 11
| new Promise((resolve) => { resolve(1) }) .then((res) => { console.log(res) kk() }) .catch((err) => { console.log(err) })
|
then/catch 的链式调用
首先, 在new Promise
中需要执行resolve
或者reject
改变状态,否则不会执行then
方法, 像下面then
就不会执行:
1 2 3 4 5 6 7 8 9 10
| var p = new Promise(function (resolve, reject) { return 1 }) p.then(function (value) { console.log(value) return value * 2 }).then(function (value) { console.log(value) })
|
在状态改变后, then
方法返回一个新的Promise
对象,因此可以通过链式调用then
方法.
catch
也可以链式调用, 因为catch
相当于then(null,err=>{})
函数的返回值将被用作创建then
返回的Promise
对象, 有如下情况:
return
一个同步的值(当没有返回时,默认返回undefined
),then
方法将返回一个resolved
状态的Promise
对象,Promise
对象的值就是这个返回值。
return
一个 Promise
,then
方法将根据这个Promise
的状态和值创建一个新的Promise
对象返回。
throw
一个同步异常,then
方法将返回一个rejected
状态的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
| var p = new Promise(function (resolve, reject) { resolve(1) }) p.then(function (value) { console.log(value) return value * 2 }) .then(function (value) { console.log(value) }) .then(function (value) { console.log(value) return Promise.resolve('resolve') }) .then(function (value) { console.log(value) return Promise.reject('reject') }) .then( function (value) { console.log('resolve: ' + value) }, function (err) { console.log('reject: ' + err) } )
|
1 2 3 4 5
| 1 2 undefined "resolve" "reject: reject"
|
再写一个return
返回promise
的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var a = new Promise((resolve) => { setTimeout(() => { resolve('5s later') }, 5000) }) var p = new Promise(function (resolve, reject) { resolve(1) }) p.then(function (value) { console.log(1) return a }).then(function (value) { console.log(value) })
|
Promise.resolve()
Promise.resolve()
(注意,是Promise.resolve()
,而不是new Promise
中的resolve
)可以接收一个值或者是一个Promise
对象作为参数。
当参数是普通值时,它返回一个resolved
状态的Promise
对象;
当参数是Promise
对象时,对象的值就是这个参数;
下面的例子可以看到 p1,p2 是相等的
1 2 3 4
| let p1 = Promise.resolve(1) let p2 = Promise.resolve(p1) p1 === p2
|
then 拿到数据发现不合适, 怎么触发 catch
throw new Error()
抛出错误. 或者 Promise.reject()
resolve 和 reject
当resolve
的参数是一个Promise
对象时,resolve
会”拆箱”获取这个Promise
对象的状态和值,但这个过程是异步的。
即最后的状态是根据传入的promise
对象的状态确定的;
如下: 执行的是resolve(a)
, 但是a
5 秒后返回的状态是rejected
,所以会执行rejected
回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var a = new Promise((resolve, reject) => { setTimeout(() => { reject('error') }, 5000) }) var p = new Promise(function (resolve, reject) { var b = resolve(a) console.log(a === b) }) p.then( function (value) { console.log('success') }, function (err) { console.log(err) return err } )
|
但是, reject
方法不同, reject
方法不存在拆箱等待, 而是直接将传入的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
| var a = new Promise((resolve, reject) => { setTimeout(() => { reject('error after 5s') }, 5000) })
var p = new Promise(function (resolve, reject) { reject(a) })
p.then(function (value) { console.log('success') }) .catch((err1) => { console.log('fail: ' + err1) return err1 }) .catch((err2) => { console.log(err2) return 1 }) .then((res) => { console.log(res) })
|
可以看到, p
中reject
了a
, 直接把a
传到了第一个 catch
, 第一个 catch 中, 打印出来参数, 就是promise
对象a
.
在 第一个catch
中return err1
, 等待 5 秒后a
变成rejected
状态, 触发第二个 catch
, 捕获到结果error after 5s
,
最后catch
可以继续链式调用, 返回1
, 相当于resolve(1)
, 最后then
打印出1
.
promise 中出现异常, promise 外的后面的代码会执行吗
会, Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
1 2 3 4 5 6
| new Promise(function (resolve, reject) { resolve(x + 1) }) console.log(333)
|
promise.all
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let a = new Promise((resolve) => { setTimeout(() => { resolve(111) }, 3000) }) let b = new Promise((resolve) => { setTimeout(() => { resolve(222) }, 1000) }) let c = new Promise((resolve) => { setTimeout(() => { resolve(333) }, 100) })
Promise.all([b, a, c]).then((res) => { console.log(res) })
|
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
| let a = new Promise((resolve) => { setTimeout(() => { console.log('haha') resolve(111) }, 3000) }) let b = new Promise((resolve, reject) => { setTimeout(() => { reject(222) }, 1000) }) let c = new Promise((resolve) => { setTimeout(() => { resolve(333) }, 100) })
Promise.all([b, a, c]) .then((res) => { console.log(res) }) .catch((err) => { console.log(err) }) 只要有一个错误, 就会catch该错误
|
Promise 如何按顺序执行
有一个数组,数组元素是返回 Promise 对象的函数,怎样顺序执行数组中的函数,即在前一个数组中的 Promise resolve 之后才执行下一个函数?
….
- 先生成数据, 下面代码执行完,生成 10 个函数组成的数组, 均返回 promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function createPromises(id) { return function () { return new Promise((resolve) => { setTimeout(() => { console.log(id) resolve(id) }, 1000) }) } }
let list = []
for (let i = 0; i < 10; i++) { list.push(createPromises(i)) }
|
1 2 3 4 5 6 7 8 9
| function promise_queue(list, i) { if (i >= 0 && i < list.length) { list[i]().then(() => { promise_queue(list, i + 1) }) } } promise_queue(list, 0)
|
1 2 3 4 5 6 7 8
| async function promise_queue(list) { let index = 0 while (index >= 0 && index < list.length) { await list[index]() index++ } } promise_queue(list)
|
- Promise.resolve()
初始状态是Promise.resolve()
, then
执行第一个, 并把下一个作为then
的success
函数(因为then
返回一个promise
的话, promise
状态改变后, 才会执行下一个then
)
1 2 3 4 5 6 7
| function promise_queue(list) { var sequence = Promise.resolve() list.forEach((item) => { sequence = sequence.then(item) }) return sequence }
|
1
| list.reduce((pre, next) => pre.then(next), Promise.resolve())
|
promise 实现每隔一段时间执行一次函数, 执行 n 次 , 不用 await
每隔 interval 毫秒, 执行一次 fn 函数, 执行 time 次:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function interval(fn, interval, time) { let f = () => new Promise((resolve) => { setTimeout(() => { fn() resolve() }, interval) }) let a = Promise.resolve() for (let i = 0; i < time; i++) { a = a.then(f) } }
|
简易版本实现 promise
promise 特性:
- 状态不可逆,从 pending => fulfilled / rejected
- 有 then、catch 方法,还有 all、race、any 方法
- 同一个 promise 可以有多个 then
- then 可以链式调用
- 已经为 fulfilled/rejected 状态时,调用 then 会立刻执行
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
| const isFunction = (variable) => typeof variable === 'function'
const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED'
class MyPromise { constructor(handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } this._status = PENDING this._value = undefined this._fulfilledQueues = [] this._rejectedQueues = [] try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } _resolve(val) { const run = () => { if (this._status !== PENDING) return const runFulfilled = (value) => { let cb while ((cb = this._fulfilledQueues.shift())) { cb(value) } } const runRejected = (error) => { let cb while ((cb = this._rejectedQueues.shift())) { cb(error) } }
if (val instanceof MyPromise) { val.then( (value) => { this._value = value this._status = FULFILLED runFulfilled(value) }, (err) => { this._value = err this._status = REJECTED runRejected(err) } ) } else { this._value = val this._status = FULFILLED runFulfilled(val) } } setTimeout(run, 0) } _reject(err) { if (this._status !== PENDING) return const run = () => { this._status = REJECTED this._value = err let cb while ((cb = this._rejectedQueues.shift())) { cb(err) } } setTimeout(run, 0) } then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext) => { let fulfilled = (value) => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } let rejected = (error) => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } switch (_status) { case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } catch(onRejected) { return this.then(undefined, onRejected) } static resolve(value) { if (value instanceof MyPromise) return value return new MyPromise((resolve) => resolve(value)) } static reject(value) { return new MyPromise((resolve, reject) => reject(value)) } static all(list) { return new MyPromise((resolve, reject) => {
let values = [] let count = 0 for (let [i, p] of list.entries()) { this.resolve(p).then( (res) => { values[i] = res count++ if (count === list.length) resolve(values) }, (err) => { reject(err) } ) } }) } static race(list) { return new MyPromise((resolve, reject) => { for (let p of list) { this.resolve(p).then( (res) => { resolve(res) }, (err) => { reject(err) } ) } }) } finally(cb) { return this.then( (value) => MyPromise.resolve(cb()).then(() => value), (reason) => MyPromise.resolve(cb()).then(() => { throw reason }) ) } }
|