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), 但是a5 秒后返回的状态是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 }) ) } }
|