Promise
Promise
的前世此生Promise
最先出如今 1988 年,由 Barbara Liskov、Liuba Shrira 独创(论文:Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems)。而且在语言 MultiLisp 和 Concurrent Prolog 中已经有了相似的实现。html
JavaScript 中,Promise
的流行是得益于 jQuery 的方法 jQuery.Deferred()
,其余也有一些更精简独立的 Promise
库,例如:Q、When、Bluebird。java
# Q / 2010 import 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
接口。jquery
new Promise( function(resolve, reject) {...} /* 执行器 */ );
想要实现一个 Promise
,必需要遵循以下规则:ios
Promise
是一个提供符合标准的 then()
方法的对象。pending
,可以转换成 fulfilled
或 rejected
状态。fulfilled
或 rejected
状态肯定,不再能转换成其余状态。ECMAScript's Promise global is just one of many Promises/A+ implementations.git
主流语言对于 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。github
因为 JavaScript 是单线程事件驱动的编程语言,经过回调函数管理多个任务。在快速迭代的开发中,由于回调函数的滥用,很容易产生被人所诟病的回调地狱问题。Promise
的异步编程解决方案比回调函数更加合理,可读性更强。ajax
传说中比较夸张的回调:编程
现实业务中依赖关系比较强的回调:axios
# 回调函数 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
梳理流程后:api
# Promise function renderPage () { const secret = genSecret() // 获取用户令牌 getUserToken(token) .then(token => { // 获取游戏列表 return getGameList(token) }) .then(data => { // 渲染游戏列表 return render(data.list) }) .then(() => { // 埋点数据上报 report() }) .catch(err => { console.error(err) }) }
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
怎么用Promise
异步编程在 Promise
出现以前每每使用回调函数管理一些异步程序的状态。
# 常见的异步 Ajax 请求格式 ajax(url, successCallback, errorCallback)
Promise
出现后使用 then()
接收事件的状态,且只会接收一次。
案例:插件初始化。
使用回调函数:
# 插件代码 let ppInitStatus = false let ppInitCallback = null PP.init = callback => { if (ppInitStatus) { callback && callback(/* 数据 */) } else { ppInitCallback = callback } } // ... // ... // 经历了一系列同步异步程序后初始化完成 ppInitCallback && ppInitCallback(/* 数据 */) ppInitStatus = true # 第三方调用 PP.init(callback)
使用 Promise:
# 插件代码 let initOk = null const ppInitStatus = new Promise(resolve => initOk = resolve) PP.init = callback => { ppInitStatus.then(callback).catch(console.error) } // ... // ... // 经历了一系列同步异步程序后初始化完成 initOk(/* 数据 */) # 第三方调用 PP.init(callback)
相对于使用回调函数,逻辑更清晰,何时初始化完成和触发回调一目了然,再也不须要重复判断状态和回调函数。固然更好的作法是只给第三方输出状态和数据,至于如何使用由第三方决定。
# 插件代码 let initOk = null PP.init = new Promise(resolve => initOk = resolve) // ... // ... // 经历了一系列同步异步程序后初始化完成 initOk(/* 数据 */) # 第三方调用 PP.init.then(callback).catch(console.error)
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
。
# 包一层 Promise function api () { return new Promise((resolve, reject) => { axios.get(/* 连接 */).then(data => { // ... // 经历了一系列数据处理 resolve(data.xxx) }) }) } # 更好的作法:利用链式调用 function api () { return axios.get(/* 连接 */).then(data => { // ... // 经历了一系列数据处理 return data.xxx }) }
Promise
实例Promise.all()
/ Promise.race()
能够将多个 Promise 实例包装成一个 Promise 实例,在处理并行的、没有依赖关系的请求时,可以节约大量的时间。
function wait (ms) { return new Promise(resolve => setTimeout(resolve.bind(null, ms), ms)) } # Promise.all Promise.all([wait(2000), wait(4000), wait(3000)]) .then(console.log) // 4 秒后 [ 2000, 4000, 3000 ] # Promise.race Promise.race([wait(2000), wait(4000), wait(3000)]) .then(console.log) // 2 秒后 2000
Promise
和 async
/ await
async
/ await
实际上只是创建在 Promise
之上的语法糖,让异步代码看上去更像同步代码,因此 async
/ await
在 JavaScript 线程中是非阻塞的,但在当前函数做用域内具有阻塞性质。
let ok = null async function foo () { console.log(1) console.log(await new Promise(resolve => ok = resolve)) console.log(3) } foo() // 1 ok(2) // 2 3
使用 async
/ await
的优点:
简洁干净
写更少的代码,不须要特意建立一个匿名函数,放入 then()
方法中等待一个响应。
# Promise function getUserInfo () { return getData().then( data => { return data } ) } # async / await async function getUserInfo () { return await getData() }
条件语句
当一个异步返回值是另外一段逻辑的判断条件,链式调用将随着层级的叠加变得更加复杂,让人很容易在代码中迷失自我。使用 async
/ await
将使代码可读性变得更好。
# Promise function getGameInfo () { getUserAbValue().then( abValue => { if (abValue === 1) { return getAInfo().then( data => { // ... } ) } else { return getBInfo().then( data => { // ... } ) } } ) } # async / await async function getGameInfo () { const abValue = await getUserAbValue() if (abValue === 1) { const data = await getAInfo() // ... } else { // ... } }
中间值
异步函数经常存在一些异步返回值,做用仅限于成为下一段逻辑的入场券,若是经历层层链式调用,很容易成为另外一种形式的“回调地狱”。
# Promise function getGameInfo () { getToken().then( token => { getLevel(token).then( level => { getInfo(token, level).then( data => { // ... } ) } ) } ) } # async / await async function getGameInfo() { const token = await getToken() const level = await getLevel(token) const data = await getInfo(token, level) // ... }
靠谱的 await
await 'qtt'
等于 await Promise.resolve('qtt')
,await
会把任何不是 Promise
的值包装成 Promise
,看起来貌似没有什么用,可是在处理第三方接口的时候能够 “Hold” 住同步和异步返回值,不然对一个非 Promise
返回值使用 then()
链式调用则会报错。
使用 async
/ await
的缺点:
async
永远返回 Promise
对象,不够灵活,不少时候我只想单纯返回一个基本类型值。
await
阻塞 async
函数中的代码执行,在上下文关联性不强的代码中略显累赘。
# async / await async function initGame () { render(await getGame()) // 等待获取游戏执行完毕再去获取用户信息 report(await getUserInfo()) } # Promise function initGame () { getGame() .then(render) .catch(console.error) getUserInfo() // 获取用户信息和获取游戏同步进行 .then(report) .catch(console.error) }
链式调用中尽可能结尾跟 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
对象才行。
Promise
当执行一个超级久的异步请求时,若超过了可以忍受的最大时长,每每须要取消这次请求,可是 Promise
并无相似于 cancel()
的取消方法,想结束一个 Promise
只能经过 resolve
或 reject
来改变其状态,社区已经有了知足此需求的开源库 Speculation。
或者利用 Promise.race()
的机制来同时注入一个会超时的异步函数,可是 Promise.race()
结束后主程序其实还在 pending
中,占用的资源并无释放。
Promise.race([anAsyncFn(), timeout(5000)])
若想按顺序执行一堆异步程序,可以使用 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
or rejected
)。async
/ await
时要考虑上下文的依赖性,避免形成没必要要的阻塞。更多文章访问个人博客