一、什么是 Promise
1.1 Promise
的前世今生
Promise
最早呈现在 1988 年,由 Barbara Liskov、Liuba Shrira 独创(论文:Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems)。并且在语言 MultiLisp 和 Concurrent Prolog 中曾经有了相似的实现。
JavaScript 中,Promise
的风行是得益于 jQuery 的办法 jQuery.Deferred()
,其余也有一些更精简独立的 Promise
库,例如:Q、When、Bluebird。
// Q / 2010import Q from 'q'function wantOdd () { const defer = Q.defer() const num = Math.floor(Math.random() * 10) if (num % 2) { defer.resolve(num) } else { defer.reject(num) } return defer.promise}wantOdd() .then(num => { log(`Success: ${num} is odd.`) // Success: 7 is odd. }) .catch(num => { log(`Fail: ${num} is not odd.`) })
因为 jQuery 并没有严格依照标准来制订接口,促使了官网对 Promise
的实现规范进行了一系列重要的廓清,该实现标准被命名为 Promise/A+。起初 ES6(也叫 ES2015,2015 年 6 月正式公布)也在 Promise/A+ 的规范上官网实现了一个 Promise
接口。
new Promise( function(resolve, reject) {...} /* 执行器 */ );
想要实现一个 Promise
,必须要遵循如下规定:
Promise
是一个提供符合标准的then()
办法的对象。- 初始状态是
pending
,可能转换成fulfilled
或rejected
状态。 - 一旦
fulfilled
或rejected
状态确定,再也不能转换成其余状态。 - 一旦状态确定,必须要返回一个值,并且这个值是不可批改的。
ECMAScript's Promise global is just one of many Promises/A+ implementations.
支流语言对于 Promise
的实现:Golang/go-promise、Python/promise、C#/Real-Serious-Games/c-sharp-promise、PHP/Guzzle Promises、Java/IOU、Objective-C/PromiseKit、Swift/FutureLib、Perl/stevan/promises-perl。
1.1.1 旨在解决的问题
因为 JavaScript 是单线程事件驱动的编程语言,通过回调函数治理多个工作。在疾速迭代的开发中,因为回调函数的滥用,很容易产生被人所诟病的回调天堂问题。Promise
的异步编程解决方案比回调函数更加正当,可读性更强。
传说中比拟夸大的回调:
事实业务中依赖关系比拟强的回调:
// 回调函数function renderPage () { const secret = genSecret() // 获取用户令牌 getUserToken({ secret, success: token => { // 获取游戏列表 getGameList({ token, success: data => { // 渲染游戏列表 render({ list: data.list, success: () => { // 埋点数据上报 report() }, fail: err => { console.error(err) } }) }, fail: err => { console.error(err) } }) }, fail: err => { console.error(err) } })}
应用 Promise
梳理流程后:
// Promisefunction renderPage () { const secret = genSecret() // 获取用户令牌 getUserToken(token) .then(token => { // 获取游戏列表 return getGameList(token) }) .then(data => { // 渲染游戏列表 return render(data.list) }) .then(() => { // 埋点数据上报 report() }) .catch(err => { console.error(err) })}
1.2 实现一个超简易版的 Promise
Promise
的运行实际上是一个观察者模式,then()
中的匿名函数充当观察者,Promise
实例充当被观察者。
const p = new Promise(resolve => setTimeout(resolve.bind(null, 'from promise'), 3000))p.then(console.log.bind(null, 1))p.then(console.log.bind(null, 2))p.then(console.log.bind(null, 3))p.then(console.log.bind(null, 4))p.then(console.log.bind(null, 5))// 3 秒后// 1 2 3 4 5 from promise
// 实现const defer = () => { let pending = [] // 充当状态并收集观察者 let value = undefined return { resolve: (_value) => { // FulFilled! value = _value if (pending) { pending.forEach(callback => callback(value)) pending = undefined } }, then: (callback) => { if (pending) { pending.push(callback) } else { callback(value) } } }}// 模仿const mockPromise = () => { let p = defer() setTimeout(() => { p.resolve('success!') }, 3000) return p}mockPromise().then(res => { console.log(res)})console.log('script end')// script end// 3 秒后// success!
二、Promise
怎么用
2.1 应用 Promise
异步编程
在 Promise
呈现之前往往应用回调函数治理一些异步程序的状态。
// 常见的异步 Ajax 申请格局ajax(url, successCallback, errorCallback)
Promise
呈现后应用 then()
接管事件的状态,且只会接管一次。
案例:插件初始化。
应用回调函数:
// 插件代码let ppInitStatus = falselet ppInitCallback = nullPP.init = callback => { if (ppInitStatus) { callback && callback(/* 数据 */) } else { ppInitCallback = callback }}// ...// ...// 经验了一系列同步异步程序后初始化实现ppInitCallback && ppInitCallback(/* 数据 */)ppInitStatus = true// 第三方调用PP.init(callback)
应用 Promise:
// 插件代码let initOk = nullconst ppInitStatus = new Promise(resolve => initOk = resolve)PP.init = callback => { ppInitStatus.then(callback).catch(console.error)}// ...// ...// 经验了一系列同步异步程序后初始化实现initOk(/* 数据 */)// 第三方调用PP.init(callback)
绝对于应用回调函数,逻辑更清晰,什么时候初始化实现和触发回调高深莫测,不再须要反复判断状态和回调函数。当然更好的做法是只给第三方输入状态和数据,至于如何应用由第三方决定。
// 插件代码let initOk = nullPP.init = new Promise(resolve => initOk = resolve)// ...// ...// 经验了一系列同步异步程序后初始化实现initOk(/* 数据 */)// 第三方调用PP.init.then(callback).catch(console.error)
2.2 链式调用
then()
必然返回一个 Promise
对象,Promise
对象又领有一个 then()
办法,这正是 Promise
可能链式调用的起因。
const p = new Promise(r => r(1)) .then(res => { console.log(res) // 1 return Promise.resolve(2) .then(res => res + 10) // === new Promise(r => r(1)) .then(res => res + 10) // 由此可见,每次返回的是实例前面跟的最初一个 then }) .then(res => { console.log(res) // 22 return 3 // === Promise.resolve(3) }) .then(res => { console.log(res) // 3 }) .then(res => { console.log(res) // undefined return '最强王者' })p.then(console.log.bind(null, '是谁活到了最初:')) // 是谁活到了最初: 最强王者
因为返回一个 Promise
构造体永远返回的是链式调用的最初一个 then()
,所以在解决封装好的 Promise
接口时没必要在里面再包一层 Promise
。
// 包一层 Promisefunction api () { return new Promise((resolve, reject) => { axios.get(/* 链接 */).then(data => { // ... // 经验了一系列数据处理 resolve(data.xxx) }) })}// 更好的做法:利用链式调用function api () { return axios.get(/* 链接 */).then(data => { // ... // 经验了一系列数据处理 return data.xxx })}
2.3 治理多个 Promise
实例
Promise.all()
/ Promise.race()
能够将多个 Promise 实例包装成一个 Promise 实例,在解决并行的、没有依赖关系的申请时,可能节约大量的工夫。
function wait (ms) { return new Promise(resolve => setTimeout(resolve.bind(null, ms), ms))}// Promise.allPromise.all([wait(2000), wait(4000), wait(3000)]) .then(console.log)// 4 秒后 [ 2000, 4000, 3000 ]// Promise.racePromise.race([wait(2000), wait(4000), wait(3000)]) .then(console.log)// 2 秒后 2000
2.4 Promise
和 async
/ await
async
/ await
实际上只是建设在 Promise
之上的语法糖,让异步代码看上去更像同步代码,所以 async
/ await
在 JavaScript 线程中是非阻塞的,但在以后函数作用域内具备阻塞性质。
let ok = nullasync function foo () { console.log(1) console.log(await new Promise(resolve => ok = resolve)) console.log(3)}foo() // 1ok(2) // 2 3
应用 async
/ await
的劣势:
简洁洁净
写更少的代码,不须要顺便创立一个匿名函数,放入
then()
办法中期待一个响应。// Promisefunction getUserInfo () { return getData().then( data => { return data } )}// async / awaitasync function getUserInfo () { return await getData()}
解决条件语句
当一个异步返回值是另一段逻辑的判断条件,链式调用将随着层级的叠加变得更加简单,让人很容易在代码中迷失自我。应用
async
/await
将使代码可读性变得更好。// Promisefunction getGameInfo () { getUserAbValue().then( abValue => { if (abValue === 1) { return getAInfo().then( data => { // ... } ) } else { return getBInfo().then( data => { // ... } ) } } )}// async / awaitasync function getGameInfo () { const abValue = await getUserAbValue() if (abValue === 1) { const data = await getAInfo() // ... } else { // ... }}
解决两头值
异步函数经常存在一些异步返回值,作用仅限于成为下一段逻辑的入场券,如果经验层层链式调用,很容易成为另一种模式的“回调天堂”。
// Promisefunction getGameInfo () { getToken().then( token => { getLevel(token).then( level => { getInfo(token, level).then( data => { // ... } ) } ) } )}// async / awaitasync function getGameInfo() { const token = await getToken() const level = await getLevel(token) const data = await getInfo(token, level) // ...}
对于多个异步返回两头值,搭配
Promise.all
应用可能晋升逻辑性和性能。// async / await & Promise.allasync function foo() { // ... const [ a, b, c ] = await Promise.all([ promiseFnA(), promiseFnB(), promiseFnC() ]) const d = await promiseFnD() // ...}
靠谱的
await
await 'str'
等于await Promise.resolve('str')
,await
会把任何不是Promise
的值包装成Promise
,看起来貌似没有什么用,然而在解决第三方接口的时候能够 “Hold” 住同步和异步返回值,否则对一个非Promise
返回值应用then()
链式调用则会报错。
应用 async
/ await
的毛病:
await
阻塞 async
函数中的代码执行,在上下文关联性不强的代码中略显累赘。
// async / awaitasync function initGame () { render(await getGame()) // 期待获取游戏执行结束再去获取用户信息 report(await getUserInfo())}// Promisefunction initGame () { getGame() .then(render) .catch(console.error) getUserInfo() // 获取用户信息和获取游戏同步进行 .then(report) .catch(console.error)}
2.5 错误处理
链式调用中尽量结尾跟
catch
捕捉谬误,而不是第二个匿名函数。因为标准里注明了若then()
办法外面的参数不是函数则什么都不做,所以catch(rejectionFn)
其实就是then(null, rejectionFn)
的别名。anAsyncFn().then( resolveSuccess, rejectError)
在以上代码中,
anAsyncFn()
抛出来的谬误rejectError
会失常接住,然而resolveSuccess
抛出来的谬误将无奈捕捉,所以更好的做法是永远应用catch
。anAsyncFn() .then(resolveSuccess) .catch(rejectError)
假使考究一点,也能够通过
resolveSuccess
来捕捉anAsyncFn()
的谬误,catch
捕捉resolveSuccess
的谬误。anAsyncFn() .then( resolveSuccess, rejectError ) .catch(handleError)
通过全局属性监听未被解决的 Promise 谬误。
浏览器环境(
window
)的回绝状态监听事件:unhandledrejection
当 Promise 被回绝,并且没有提供回绝处理程序时,触发该事件。rejectionhandled
当 Promise 被回绝时,若回绝处理程序被调用,触发该事件。
// 初始化列表const unhandledRejections = new Map()// 监听未解决回绝状态window.addEventListener('unhandledrejection', e => { unhandledRejections.set(e.promise, e.reason)})// 监听已解决回绝状态window.addEventListener('rejectionhandled', e => { unhandledRejections.delete(e.promise)})// 循环解决回绝状态setInterval(() => { unhandledRejections.forEach((reason, promise) => { console.log('handle: ', reason.message) promise.catch(e => { console.log(`I catch u!`, e.message) }) }) unhandledRejections.clear()}, 5000)
留神:Promise.reject()
和 new Promise((resolve, reject) => reject())
这种形式不能间接触发 unhandledrejection
事件,必须是满足曾经进行了 then()
链式调用的 Promise
对象才行。
2.6 勾销一个 Promise
当执行一个超级久的异步申请时,若超过了可能忍耐的最大时长,往往须要勾销此次申请,然而 Promise
并没有相似于 cancel()
的勾销办法,想完结一个 Promise
只能通过 resolve
或 reject
来扭转其状态,社区曾经有了满足此需要的开源库 Speculation。
或者利用 Promise.race()
的机制来同时注入一个会超时的异步函数,然而 Promise.race()
完结后主程序其实还在 pending
中,占用的资源并没有开释。
Promise.race([anAsyncFn(), timeout(5000)])
2.7 迭代器的利用
若想按程序执行一堆异步程序,可应用 reduce
。每次遍历返回一个 Promise
对象,在下一轮 await
住从而顺次执行。
function wasteTime (ms) { return new Promise(resolve => setTimeout(() => { resolve(ms) console.log('waste', ms) }, ms))}// 顺次节约 3 4 5 3 秒 === 15 秒const arr = [3000, 4000, 5000, 3000]arr.reduce(async (last, curr) => { await last return wasteTime(curr)}, undefined)
三、总结
- 每当要应用异步代码时,请思考应用
Promise
。 Promise
中所有办法的返回类型都是Promise
。Promise
中的状态扭转是一次性的,倡议在reject()
办法中传递Error
对象。- 确保为所有的
Promise
增加then()
和catch()
办法。 - 应用
Promise.all()
行运行多个Promise
。 - 假使想在
then()
或catch()
后都做点什么,可应用finally()
。 - 能够将多个
then()
挂载在同一个Promise
上。 async
(异步)函数返回一个Promise
,所有返回Promise
的函数也能够被视作一个异步函数。await
用于调用异步函数,直到其状态扭转(fulfilled
orrejected
)。- 应用
async
/await
时要思考上下文的依赖性,防止造成不必要的阻塞。
版权申明
本博客所有的原创文章,作者皆保留版权。转载必须蕴含本申明,放弃本文残缺,并以超链接模式注明作者后除和本文原始地址:https://blog.mazey.net/understand-promise
(完)