承诺我已说出口。
面试环节
先来看一段代码:
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一个then"); new Promise((resolve, reject) => { console.log("内部promise"); resolve(); }) .then(() => { console.log("内部第一个then"); }) .then(() => { console.log("内部第二个then"); }); return new Promise((resolve, reject) => { console.log("内部promise2"); resolve(); }) .then(() => { console.log("内部第一个then2"); }) .then(() => { console.log("内部第二个then2"); }); }) .then(() => { console.log("外部第二个then"); });
|
Are you ok ? 恭喜你被谷歌录取了 : 行了行了下一位
Why
首先我们先来了解一下引入 Promise
的必要性。
回调地狱
曾几何时(现在也是),我们的异步代码是这样的:
post('/user/login', function (err, data) { if (err) return err; get('/user/info', function (err, data) { if (err) return err; post('/articles/create', function (err, data) { if (err) return err; get('/articles/list', function (err, data) { ... }); }); }); });
|
这就是大家津津乐道的 回调地狱 了,主要有以下几个问题:
- 嵌套太深,逻辑复杂:每层的回调函数的业务逻辑都依赖于上层执行的返回结果,嵌套层次多了之后,代码可读性很差。
- 错误处理麻烦:每层的回调函数都需要传入两个状态(失败、成功),且每一层都需要对错误进行单独处理,没有统一的错误处理机制。
- 上下文环境乱:有的时候我们想处理上层环境(调用
this
),却发现绑定不到,只能使用 var _this = this;
这样的 hack 方法。
Promise
这时候天空一声巨响,Promise
闪亮登场,救人民与水深火热之中。代码如下:
const post = (url) => { return new Promise((resolve, reject) => { post(url, (err, data) => { err && reject(err) resolve(data) }) }) }
post('/user/login') .then(data => get('/user/info')) .then(data => post('/articles/create')) .then(data => get('/articles/list')) .then(...) .catch(err => console.error('报错了啦:' + err))
|
这不是好起来了嘛:
- 干掉了嵌套调用,采用了链式挂载,逻辑上清晰了一些。
- 合并处理错误,在最后统一
catch
并执行。
async / await
Emmm,还不是一坨代码。好吧,再来看看终极方案 async/await
:
const do = async () => { try { const token = await post('/user/login'); const userInfo = await get('/user/info'); const createArticleResult = await post('/articles/create'); const articlesList = await get('/articles/list'); ... } catch (err) { console.error('又报错了啦:' + err) } }
|
直接屏蔽了异步逻辑,改用同步方式。
wo
API
Promise - MDN
工欲善其事,必先利其器。
原理
那么 Promise
是如何工作的呢?主要是采用了 延迟绑定 和 返值穿透 两种思想。
延迟绑定
const p = new Promise((resolve, reject) => { resolve('ok') })
...
p.then((value) => { console.log(value) })
|
可以看到先创建了应该 Promise
对象,此时还没有绑定 回调函数
。创建完对象之后,可以处理其他代码逻辑。直到调用 then
处理执行逻辑的时候,我们才将 回调函数
进行绑定。创建对象 和 绑定回调 实现了分离解耦。
返值穿透
const p1 = new Promise((resolve, reject) => { resolve(1) })
const p2 = p1.then((value) => { const p2 = new Promise((resolve, reject) => { resolve(value + 1) }) return p2 })
p2.then((value) => { console.log(value) })
|
可以看到,在 p1
的回调函数中我们创建了一个新的 Promise
对象 p2
,将它 return
到了最外层并被接受。每层回调函数的返回值始终可以穿透到最外层,这样就可以保证 返回值
始终可控,不会陷入循环中。
源码
上代码啊
其实经典方法的实现原理都大同小异:状态流传递、队列缓存回调……
const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED'
class MyPromise { constructor(handle) { 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) { if (this._status !== PENDING) return this._status = FULFILLED this._value = val }
_reject(err) { if (this._status !== PENDING) return this._status = REJECTED this._value = err }
then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext) => { let fulfilled = value => { try { let res = onFulfilled(value); if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } catch (err) { onRejectedNext(err) } } let rejected = error => { try { 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 } }) } }
|
参考资料
大厂面试必考知识点:Promise 注册微任务和执行过程