写在前面:
Promise这一章的顺序对于未接触过使用过Promise的童鞋而言略抽象了,前边几章主要为了说明Promise和以前的异步方式相比有什么优点和它能解决什么问题,后边才详解Promise的API设计和各类场景下如何使用Promise。node
建议先了解和简单使用过Promise后再阅读,效果更佳。git
正文github
以前的方式:api
Promise方式:
第三方提供了解其任务什么时候结束的能力数组
Promise的异步特性是基于任务的(图示以下)promise
一种处理异步的思路:为了统一如今和未来,把它们都变成未来,即全部操做都成了异步的浏览器
书中关于Promise是个啥的观点:安全
一种封装和组合将来值的易于复用的机制
一种在异步任务中做为两个或更多步骤的流程控制机制,时序上的this-then-that —— 关注点分离
Promise设计的重要基础并发
- Promise必定是异步执行的,即便是当即完成的Promise(相似 new Promise((resolve)=>{ resolve(42) })),也没法被同步观察到
- 一旦Promise决议,它就永远保持在这个状态,变成了不变值(immuatable value),这是设计中最基础和最重要的因素
- Promise至多只能有一个决议值(一个!一个!一个!)
引伸:异步
基于thenable的鸭子类型
if( p !== null && ( typeof p === 'object' || typeof p === 'function' ) && typeof p.then === 'function' ) { // 假定这是一个thenable } else { // 不是thenable }
这种方式显然是有些问题的,可是目前通用的方式
信任问题见 异步篇
避免Zalgo这类反作用:一个任务有时同步完成,有时异步完成,可能致使竞态条件
Promise从定义上保证了不会存在这种问题:参考3.1 设计基础 — 即便是当即完成的Promise,也没法被同步观察到
Note: 调用过晚强调的是调用顺序?
Promise建立对象调用resolve(..)或reject(..)时,这个Promise的then注册的观察回调就会自动调度(注意是被调度而不是执行) —— 在下一个异步时机点上依次被调用执行,它们相互之间是不会互相影响或延误的
Promise一旦决议则必定会通知决议(传入then的完成回调或拒绝回调调用),即便是Javascript运行错误也会调用拒绝回调
若是某个Promise一直不决议呢?使用竞态的高级抽象机制:
// 超时工具 function timeoutPromise(delay){ return new Promise( (resolve, reject) => { setTimeout( function () { reject('Timeout!'); }, delay); } ) } // 设置某个Promise foo()超时 Promise.race( [ foo(), timeoutPromise(3000) ] ) .then( function () { // foo(..)及时完成 }, function (err) { // foo(..)被拒绝或者超时 // 经过查看err肯定错误状况 } );
若是建立Promise的代码试图屡次调用resolve(..)或reject(..),或者二者都调用,Promise只会接受第一次决议,后续调用都会被忽略
Promise至多只能有一个决议值
若是使用多个参数调用resolve(..)或reject(..),第一个参数以后的全部参数都会被忽略
Promise其实也是传入回调函数,故函数中照样能根据做用域规则访问到对应的环境数据
这里说的错误或异常可能出如今两个过程:
查看其决议结果过程当中任什么时候间点
,我的认为可能翻译得有点问题,应该要强调是其决议以前)这两种错误都不会被丢弃,但针对它们的处理方式有所不一样:
针对1:
该Promise会被当即拒绝,但注意这个异常也被变成了异步行为
let p = new Promise ( function(resolve, reject){ foo.bar(); // foo undefined 将抛出错误 Promise=>reject resolve( 42 ); // 不会执行到这里 }); p.then( function fulfilled(){ // 不会执行到这里 }, function rejected(err){ // err是一个TypeError异常 } )
针对2:
这个时候当前Promise已经决议,其决议结果是个不可变值
then(..)调用返回的下一个Promise被拒绝
let q = new Promise ( function(resolve, reject){ resolve( 42 ); }) q.then( function fulfilled(){ foo.bar(); // foo undefined 将抛出错误 致使then返回的Promise被reject }, function rejected(err){ // 不会执行到这里 } ).then( function fulfilled(){ // 不会执行到这里 }, function rejected(err){ // err是一个TypeError异常 } )
Promise.resolve(..) 规范化传入的值:
具体看例子(传入Promise的状况略)
// 传入一个当即值 let p = Promise.resolve(42); p.then( res => { console.log('Promise.resolve(42).then:',res); }) let p1 = Promise.resolve({}); p1.then( res => { console.log('Promise.resolve({}).then:',res); }) // 传入一个 thenable 尝试展开 let p2 = Promise.resolve({ then: function(cb) { cb(42)} }); p2.then( res => { console.log('Promise.resolve(thenable).then:', res); }, err => { console.log('Promise.resolve(thenable).then:', err); }) // 注意 这种状况其实也是当即值!!! let p3 = Promise.resolve( setTimeout(()=>{ return 'inside a continuation' },1000) ); // settimeout函数返回当前定时器引用=>耶 当即值 p3.then( res => { console.log('Promise.resolve(看起来是个异步).then:', res); })
Promise不只仅是一个单步执行this-then-that的操做机制,这只是它的构成部件,实际上Promise是能够链接到一块儿使用表示一系列异步步骤:
再仔细看看第二点,结合上文 3.3.7 Promise.resolve(..)的能力,这是Promise链式流在每一步都能有异步能力的关键!
栗子:
// 返回当即值 let p = Promise.resolve(21); p .then( function(v) { console.log(v); // 21 // 返回当即值 return v * 2; }) // 这里是连接的Promise .then ( function(v) { console.log(v); // 42 });
// 返回Promise并引入异步 let p = Promise.resolve(21); p .then ( function(v) { // 返回一个异步Promise return new Promise( (resolve, reject) => { setTimeout(() => { resolve(v*2); }, 1000); }); }) .then ( function(v) { // 前一步延迟1s后执行 console.log(v); })
Promise链不只仅是一个表达多步异步序列的流程控制,还能够从一个步骤到下一个步骤的消息通道
几种错误处理方式:
try...catch结构不能应用于异步模式
function foo() { setTimeout(() => { baz.bar(); // 错误代码 }, 100); } try{ foo(); // 以后将抛出全局错误 } catch (err) { // 不会走到这里 }
foo()中有本身的异步完成函数,其中任何异步错误都没法捕捉到
node.js api或库中常见的err-first模式
function foo(cb) { setTimeout(() => { try { var x = baz.bar(); // 错误代码 cb(null, x); } catch (err) { cb(err); } }, 100); } foo( function(err, val) { if(err) { console.error(err); // 报错惹 } else { console.log(val); } })
分离回调模式(split-callback)
一个回调用于完成状况,一个回调用于拒绝状况
Promise采用的就是这种方式
先参考 3.3.6 再进行详细讨论:
Promise决议前、决议后产生的错误处理方式有所不一样
错误的使用Promise API产生的错误会阻碍正常Promise对象的构造,这种状况下会当即抛出异常(这种状况应该死都不要出现 0 0)
因为Promise链式特色,其链上的最后一步,不论是什么,老是存在着在未被查看的Promise中出现未捕获错误的可能性
即理论上来讲:总有可能有错误未被捕获,而出现全局报错
P.S. 这也是我的认为使用Promise最头疼的一点
关于如何解决3.5.1提出问题的一些思路
该小节讨论的是从做者角度提出一种避免在使用Promise时在开发者未注意的状况下出现未捕获错误而报出全局错误的方案
具体请看:
{ let p = Promise.reject(21); // 将触发全局报错 Uncaught (in promise) 21 let p1 = Promise.reject(21).then ( // 拒绝前,注册了一个错误处理函数 (res) => { // 不会走到这里 }, (err) => { console.log(`注册了一个错误处理函数:${err}`); } ) Promise.prototype.defer = function (){ // 做者提出的一个API // 简单实现就是单纯的返回这个Promise自己 return this; } let p2 = Promise.reject(21).defer(); // p2的结果在未来会被查看,如今暂时不要报全局错误 let foo = Promise.resolve(21); foo .then (function(v) { return p2; // 这里查看p2的结果 }, function (err) { // 不会走到这里 }) .catch (function(v) { console.log(v); // p2的结果 }) }
基于Promise构建的异步抽象模式
相似门(gate)这种机制:须要等待两个或更多并行/并发的任务都完成才能继续,它们的完成顺序并不重要,但必须都要完成,门才能打开并让流程控制继续
Promise.all([ .. ])的参数接收一个数组:
3.3.7 构建可信任的Promise
)返回一个Promise:
每一个promise都必须关联一个拒绝/错误处理函数,特别是从Promise.all([ ... ])返回的那一个
相似门闩(shuan),竞态:一旦有任何一个Promise决议为完成,就标记为完成;一旦有任何一个Promise决议为拒绝,它就会拒绝
Promise.race([ ... ])的参数接收一个数组:
返回一个Promise:
关于这两个API须要注意
在all和race中存在着被忽略或丢弃的promise,若是这些promise中保存着重要的数据或资源或者开发者须要记录这些promise失败的事实,又该怎么办呢?
finally API就是基于这种状况提出的:Promise须要一个finally(...)回调注册,这个回调在Promise决议后老是会被调用,并容许执行任何须要的清理工做
注:书中提到finally还未被规范支持,而在18年1月已经正式加入到提案中了,可参考 https://github.com/tc39/propo... 和 https://github.com/tc39/propo...
书中还提到了一种观察模式(基于同一个Promise决议能够被屡次查看),具体能够看栗子
let foo = new Promise((resolve, reject) => { setTimeout(() => { resolve(21); }, 301); }); let timeout = function(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('timeout'); }, time); }) } // foo会被默默忽略 Promise.race( [ foo, timeout(300) ]) .then( (res) => { console.log(`Promise.race: ${res}`); }) .finally( (res) => { console.log(`Promise.race: ${res}`); // finally回调是不会提供任何参数的,详情可看 https://github.com/tc39/proposal-promise-finally }) // 观察者模式 if(!Promise.observe){ Promise.observe = function(pr, cb){ // 观察pr的决议 pr.then( function fulfilled (msg){ // 完成时 Promise.resolve(msg).then(cb); }, function reject (msg){ // 拒绝时 传递错误消息 但注意观察者promise是resolve的 Promise.resolve(msg).then(cb); } ); // 返回最初的promise return pr; } } // 仍是上一个超时的例子 Promise.race( [ Promise.observe( foo, function cleanup (msg){ console.log(`Promise.observe: ${msg}`); // foo即便没有在超时以前完成 也能够获取其决议状况 } ) .then ])
略
@TODO 自行实现 Promise.any finally map等扩展API
实现一个异步的map(..)工具
这里也主要看栗子
if(!Promise.map) { Promise.map = function(vals, cb) { // 等待全部map的promise决议的新的promise return Promise.all( // 对vals使用map将每一个值转出promise,值数组->Promise数组 vals.map( function(val){ // 将val值替换成调用cb函数后决议的新的promise return new Promise( function(resolve){ // resolve reject传入到cb函数中 cb(val, resolve); }) }) ) } } // 使用Promise.map var p1 = Promise.resolve(21); var p2 = Promise.resolve(30); var p3 = Promise.reject('opps'); Promise.map( [p1,p2,p3], function(pr, resolve){ Promise.resolve(pr) .then( val => { resolve( val*2 ); }, resolve // 注意:不能发出拒绝信号,若是发出会致使Promise.map被拒绝,其余map结果也会被丢弃 ) }) .then( (vals) => { console.log(vals); })
TODO:Promise API 概述详解单独成篇