第十一章:期约
介绍Promise
之前,先介绍一下异步编程
;
同步行为与异步行为在计算机科学中一对对立统一的概念
- 同步行为:内存中严格按照顺序执行处理器指令。在执行的每一步,都可以推断出程序的状态。
- 异步行为:类似于系统中断,即:当前进程外部的实体可以执行的代码;
11.1 回调地狱
在早期JavaScript
中,只支持定义回调函数
来表明异步;通常需要深度嵌套的回调函数(回调地狱)来解决;
function double(value){
setTimeout( () => setTimeout(console.log, 0, value * 2), 1000);
}
double(3); // 大概1000毫秒后,6
-
异步操作值
在内部
setTimeout
中可以异步操作传入值value
;所以可以提取一个回调函数
来利用;function double(value,callback){ setTimeout( () => setTimeout(() => callback(value *2) ), 1000); } double(3, (x) => { console.log(`I get the ${x}!`)} ); // 大概1000毫秒后,I get the 6!
-
失败处理
上面的异步操作值,达成一种成功处理的效果。反之,也需要实现失败处理的效果;
const successCallback = (x) => { console.log(`Success:${x}`); }; const faliureCallback = (e) => { console.log(`Failure:${e}`); }; function double(value,success,faliure){ setTimeout( () => { try{ if( typeof value !== 'number' ) { throw 'Yout must input a number as first argument'; } success(2*value); // 成功处理 }catch(e){ faliure(e); // 失败处理 } }, 1000); } double(3, successCallback, faliureCallback); // 大概1000毫秒后,Success:6 double('a', successCallback, faliureCallback); // 大概1000毫秒后,Failure:Yout must input a number as first argument
这种模式已经不可取了,因为必须初始化异步操作时定义回调。
-
嵌套异步回调
假如一个异步的返回值,被另一个异步使用呢?
function double(value,success,faliure){ setTimeout( () => { try{ if( typeof value !== 'number' ) { throw 'Yout must input a number as first argument'; } success(2*value); // 成功处理 }catch(e){ faliure(e); // 失败处理 } }, 1000); } const successCallback = (x) => { double(x, (y) => console.log(`Success:${ y }`)) }; // 嵌套调用 const faliureCallback = (e) => { console.log(`Failure:${e}`); }; double(3, successCallback, faliureCallback); // 大概1000毫秒后,Success:12
显然,这一层嵌套已经不好维护了。该回调策略显然不可用!(回调地狱,真的是地狱)
11.2 期约
ES6新增了Promise类型,成为了主导性的异步编程机制。如fetch()
和Battery Status API
等浏览器API都是以期约为基础;
11.2.1 期约基础
-
期约需要new操作符来实例化;
-
创建期约的时候,需要传入执行器(不然会报错);
let p = new Promise( ()=>{} ); setTimeout(console.log, 0, p); // Promise { <pending> }
-
期约状态机
期约拥有状态,且是私有的,不能直接通过
JavaScript
检测、修改;只能改变一次!pending
:期约的最初始状态——待定;表示尚未开始或正在执行中。resolved
:期约可以落定,落定成功即为解决(resolved
),有时候也称兑换(fulfilled
);rejected
:期约可以落定,落定失败即为拒绝(rejected
);
-
解决值与拒绝理由
当状态为兑现的时候,会返回一个解决值;当为拒绝的时候,会返回一个拒绝理由;
-
期约的使用例子
期约向服务器发送一个HTTP请求并预定会返回一个JSON。 如果请求返回范围在200~299的状态码, 则足以让期约的状态变为兑现。 此时期约内部就可以收到一个JSON字符串。 类似地, 如果请求返回的状态码不在200~299这个范围内, 那么就会把期约状态切换为拒绝。 此时拒绝的理由可能是一个Error对象, 包含着HTTP状态码及相关错误消息。
-
通过执行函数控制期约状态
执行函数主要有两个职责:
- 初始化期约的异步行为
- 控制状态的最终转换:
resolved()
和reject()
;
因为执行器是期约的初始化程序,所以执行器函数是同步函数;
let p1 = new Promise( (resolve,reject)=>{ resolve(); } ); setTimeout(console.log, 0, p1); // Promise { undefined } let p2 = new Promise( (resolve,reject)=>{ reject(); } ); setTimeout(console.log, 0, p2); // Promise { <rejected> undefined }
除此之外,还可以延迟状态切换:
let p1 = new Promise( (resolve,reject)=>{ setTimeout(()=>{ resolve() },5000); } );
注意:状态切换是不可逆的,当进行二次切换的时候,是静默失败的
let p1 = new Promise( (resolve,reject)=>{ resolve(); reject(); // 静默失败 } ); setTimeout(console.log, 0, p1); // Promise { undefined }
-
Promise.resolve()
期约并非一开始就必须处于创建状态;而是可以通过静态方法:
Promise.resolve()
来创建一个已解决的状态的期约;