0
点赞
收藏
分享

微信扫一扫

《JavaScript高级程序设计》- 第十一章:期约

椰果玩安卓 2022-02-25 阅读 54

第十一章:期约

介绍Promise之前,先介绍一下异步编程

  1. 同步行为:内存中严格按照顺序执行处理器指令。在执行的每一步,都可以推断出程序的状态。
  2. 异步行为:类似于系统中断,即:当前进程外部的实体可以执行的代码;

11.1 回调地狱

在早期JavaScript中,只支持定义回调函数表明异步;通常需要深度嵌套的回调函数(回调地狱)来解决;

function double(value){
    setTimeout( () => setTimeout(console.log, 0, value * 2), 1000);
}
double(3);	// 大概1000毫秒后,6
  1. 异步操作值

  2. 失败处理

  3. 嵌套异步回调

11.2 期约

ES6新增了Promise类型,成为了主导性的异步编程机制。如fetch()Battery Status API等浏览器API都是以期约为基础;

11.2.1 期约基础

  1. 期约需要new操作符来实例化;

  2. 创建期约的时候,需要传入执行器(不然会报错);

    let p = new Promise( ()=>{} );
    setTimeout(console.log, 0, p);	// Promise { <pending> }
    
  3. 期约状态机

    • pending:期约的最初始状态——待定;表示尚未开始或正在执行中。
    • resolved:期约可以落定,落定成功即为解决resolved),有时候也称兑换fulfilled);
    • rejected:期约可以落定,落定失败即为拒绝rejected);
  4. 解决值与拒绝理由

  5. 期约的使用例子

  6. 通过执行函数控制期约状态

  7. Promise.resolve()

    期约并非一开始就必须处于创建状态;而是可以通过静态方法:Promise.resolve()来创建一个已解决的状态的期约

    将任何值转化为期约:

    如果传入参数也是期约,则操作是幂等的

  8. Promise.reject()

    Promise.reject()类似,Promise.reject()会实例化一个拒绝的期约抛出一个异步错误;这个异步错误不能被try{}catch{}捕获;

  9. 同步、异步执行的二元性

    Promise很大程度上会导致一种完全不同的JavaScript计算模式:

    try{
        throw new Error('This is a sync error!');
    }catch(e){
        console.log(e);		// 可以正常捕获错误
    }
    
    try{
        Promise.reject( new Error('This is a async error!') );
    }catch(e){
    	console.log(e);
    }
    

11.2.2 期约的实例方法

期约实例是链接外部同步代码与内部异步代码之间的桥梁;而实例的方法有如下功能:

  • 访问异步操作返回值(resolve,reject)
  • 处理期约成功和失败的结果(then)
  • 连续对期约求值()
  • 添加期约终止时才会执行的代码(finally)
  1. Promise.prototype.then()

  2. Promise.prototype.catch()

  3. Promise.prototype.finally()

  4. 非重入期约方法

    • 当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而不是立即执行
    • 跟在这些处理程序后面的同步代码,一定会比处理程序先执行
    • 以上称为“非重入”特性
    let p = Promise.resolve();  // 创建已解决期约
    
    p.then(()=>{
        setTimeout(console.log,0,'resolved');
    },null);
    
    console.log('this code is after p.then()!');
    // 实际上输出:
    // this code is after p.then()!
    // resolved
    

    p.then()调用onResolve处理程序的时候,会将其推进消息队列;当线程上同步代码处理完毕后才会处理消息队列中的异步代码

  5. 临近处理程序的执行顺序

  6. 传递解决值拒绝理由

    当状态落定后,期约会提供解决值或拒绝理由,并作为resolve()reject()的第一个参数向后传递;

  7. 拒绝期约拒绝错误处理

    拒绝期约代表了一种程序状态,即需要中断或特殊处理;在期约执行器函数或处理程序中抛出错误,则会导致拒绝,对于的错误对象,就是拒绝理由

11.2.3 期约连锁与期约合成

  • 期约连锁:将期约一个一个拼接起来
  • 期约合成:将多个期约合并为一个期约
  1. 期约连锁:

    由于期约的实例方法,都会返回一个新的期约实例,所以可以形成链式连锁调用

    let p = new Promise((resolve,reject) => {
        setTimeout(console.log,0,'first');
        resolve();
    });
    p.then(() => { setTimeout(console.log,0,'second') },null)
     .then(() => { setTimeout(console.log,0,'third') },null)
     .then(() => { setTimeout(console.log,0,'fourth') },null)
    
    // first
    // second
    // third
    // fourth
    

    显然,这样链式调用也可以使用在其他实例方法上

    let p = new Promise((resolve,reject) => {
        setTimeout(console.log,0,'new');
        reject();
    })
    p.then( ()=> setTimeout(console.log,0,'then') )
     .catch( ()=> setTimeout(console.log,0,'catch') )
     .finally( ()=> setTimeout(console.log,0,'finally') )
    // new
    // catch
    // finally
    
  2. 期约图:

    由上可知,期约可以由多个处理程序,所以就会形成一个有向非循环的图;

    let A = new Promise( (resolve,reject) => {
        console.log('A');
        resolve();
    } );
    
    let B = A.then( ()=> { console.log("B"); } );
    let C = A.then( ()=> { console.log("C"); } );
    
    C.then( () => { console.log("D") } );
    C.then( () => { console.log("E") } );
    
    /*
        A
       / \
      B   C
         / \
        D   E
    */
    
  3. 期约合成:

11.3 异步函数

异步函数,又被称为“async/await”(语法关键字);是ES8规范新增的;

作用在于:以同步方式写的代码可以异步执行

11.3.1 async

可以让函数具有异步特征

  1. 使用async

    async function show(){
        console.log(1);
    }
    show();
    console.log(2);
    // 1
    // 2
    
  2. 异步函数返回值

    如果异步函数使用return返回值,则该值会被Promise.resolve()包装为一个期约对象

  3. 注意:

11.3.2 await

暂停异步代码的执行,等待期约解决;

  1. 使用await

    async function show(){
    	console.log(1);
        let p = new Promise( (resolve,reject) => {
            setTimeout(resolve,2000,3);
        })
        console.log(await p);
    }
    
  2. await的限制

    • 必须在async函数中使用
    • 不能在顶级上下文,如:script标签、模块中使用;

11.3.3 理解异步函数执行顺序

await关键字并非只是等待一个值可用而已;JavaScript运行时在碰到await关键字的时候,会记录在哪里暂停执行,等到await右边的值可用的时候,JavaScript运行时会向消息队列推送一个任务,这个任务会恢复异步函数的执行。

async function show(){
    console.log(2);
    await null;
    conosole.log(4);
}

console.log(1);
show();
console.log(3)

注意:如果await等待的不是一个立即可用的值,则会发生另外的改变;

async function foo() {
    console.log(2);
    console.log(await Promise.resolve(8));
    console.log(9);
}
async function bar() {
    console.log(4);
    console.log(await 6);
    console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);

11.3.4 异步函数策略

  1. 实现sleep()

    async function sleep(delay){
        return new Promise( (resolve) => setTimeout(resolve,delay));
    }
    
    async function test(){
        const startTime = Date.now();
        await sleep(3000);          // 利用await暂停3秒左右
        console.log(Date.now() - startTime);
    }
    
    test(); 
    
  2. 利用平行执行

  3. 串行执行期约

  4. 栈追踪与内存管理

11.4 小结

举报

相关推荐

0 条评论