promise

状态

ECMAScript 6新增的引用类型Promise,可以通过new 操作符来实例化;ECMScript 6增加了对Promises/A+ 规范的完善支持;很多浏览器API(如fetch() 和Battery Status API) 也以期约为基础。

promise 状态

  • 待定(pending)
  • 兑现(fulfilled, 有时候也称为’解决’,resolved)
  • 拒绝(rejected)

待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。无论落定为哪种状态都是不可逆的。只要从待定状态转换为兑现或拒绝,期约的状态就不再改变。

期约的状态时私有的,期约的状态不能被外部JavaScript 代码修改。 期约故意将异步行为封装起来,从而隔离外部的同步代码。

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

由于期约的状态时私有的,所以只能在内部进行操作=> 执行函数中完成。执行函数主要由两项职责:初始化期约的异步行为和控制状态的最终转换。其中控制期约状态的转换是通过调用它的两个函数参数实现的。

  • resolve() 调用状态会切换为兑现;
  • reject() 调用状态会切换为拒绝,并抛出错误;

调用其中任何函数参数后,状态就已经落定了,再调用其他函数不会改变状态了;

    let p = new Promise((resolve, reject) => {
        resolve();      
        reject();//没有效果    
    });   

静态方法

Promise.resolve()

通过调用Promise.resolve() 静态方法,可以实例化一个解决的期约。

    <!--等同-->
    let p1 = new Promise((resolve, reject) => resolve());   
    let p2 = Promise.resolve();

介绍第一个参数,默认返回undefined

这个解决的期约的值对应着传给Promise.resolve() 的第一个参数。

    setTimeout(console.log, 0, Promise.resolve());    
    // Promise <resolved>: undefined    
    setTimeout(console.log, 0, Promise.resolve(3));    
    // Promise <resolved>: 3   
    // 多余的参数会忽略    
    setTimeout(console.log, 0, Promise.resolve(4, 5, 6));    
    // Promise <resolved>: 4

幂等方法

传入参数是一个期约,类似于空包装,返回原始值

    let p = Promise.resolve(7);   
    setTimeout(console.log, 0, p === Promise.resolve(p));    
    // true    
    setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));   
    // true

幂等性会保留传入期约的状态:默认的pendding 状态不会被resolve改变

    let p = new Promise(() => {});   
    setTimeout(console.log, 0, p);                      
    // Promise <pending>   
    setTimeout(console.log, 0, Promise.resolve(p));
    // Promise <pending>    
    setTimeout(console.log, 0, p === Promise.resolve(p)); 
    //true

Promise.reject()

通过调用Promise.reject() 静态方法,可以实例化一个解决的期约并抛出一个异步错误(这个错误不能通过try/catch 捕获,而只能通过拒绝处理程序捕获)。

    <!--等同-->
    let p1 = new Promise((resolve, reject) => reject());    
    let p2 = Promise.reject();

Promise.reject 异常不能通过try/catch捕获;拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。

    try {    
        throw new Error('foo');    
    } catch(e) {    
         console.log(e); // Error: foo  
    }   
    try {     
         Promise.reject(new Error('bar')); 
    } catch(e) {      
         onsole.log(e); 
    }    
    //Uncaught(inpromise)Error: bar

Promise.all()

Promise.all() 静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代对象,返回一个新期约:

    let p1 = Promise.all([Promise.resolve()]);
    // 可迭代对象中的元素会通过Promise.resolve()转换为期约    
    let p2 = Promise.all([3, 4]);
    // 空的可迭代对象等价于Promise.resolve()  
    let p3 = Promise.all([]);
    // 无效的语法  
    let p4 = Promise.all();
    // TypeError: cannot read Symbol.iterator of undefined

如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的期约也会拒绝:

     // 永远待定   
    let p1 = Promise.all([new Promise(() => { })]);
    setTimeout(console.log, 0, p1); // Promise <pending>  
    // 一次拒绝会导致最终期约拒绝    
    let p2 = Promise.all([
        Promise.resolve(),
        Promise.reject(),
        Promise.resolve()
    ]);
    setTimeout(console.log, 0, p2); // Promise <rejected> 
    // Uncaught (in promise) undefined

如果所有期约都成功解决,则合成期约的解决值会按顺序返回

    let p = Promise.all([
        Promise.resolve(3),
        Promise.resolve(),
        Promise.resolve(4)
    ]);
    p.then((values) => setTimeout(console.log, 0, values)); // [3, undefined, 4]

如果其中有期约会拒绝了,会带上第一拒绝的期约理由返回;

    let p = Promise.all([
        Promise.resolve(3),
        Promise.reject(),
        Promise.resolve(4)
    ]);
    p.then((values) => {
        console.log("values:",values);
    },err=>{
        console.log("err:",err); // undefined
    }); 

Promise.race()

Promise.race() 静态方法返回一个包装期约,是一组集合中只要有一个落定的期约就会返回。这个方法接收一个可迭代对象,放回一个新期约:

    let p1 = Promise.race([
        Promise.resolve(),
        Promise.resolve()
    ]);
    // 可迭代对象中的元素会通过Promise.resolve()转换为期约  
    let p2 = Promise.race([3, 4]);
    // 空的可迭代对象等价于new Promise(() => {})    
    let p3 = Promise.race([]);
    // 无效的语法  
    let p4 = Promise.race();
    // TypeError: cannot read Symbol.iterator of undefined

Promise.race() 不会对解决或解拒绝的期约区别对待。无论是解决还是拒绝,只要是第一个落定的期约,Promise.rance()就会包装其解决值或拒绝理由并返回新期约,之后的期约不会影响最终结果;

 // 解决先发生,超时后的拒绝被忽略    
    let p1 = Promise.race([
        Promise.resolve(3),
        new Promise((resolve, reject) => setTimeout(reject, 1000))
    ]);
    setTimeout(console.log, 0, p1); // Promise <resolved>: 3   
    // 拒绝先发生,超时后的解决被忽略   
    let p2 = Promise.race([
        Promise.reject(4),
        new Promise((resolve, reject) => setTimeout(resolve, 1000))
    ]);
    setTimeout(console.log, 0, p2); // Promise <rejected>: 4   
    // 迭代顺序决定了落定顺序  
    let p3 = Promise.race([
        Promise.resolve(5),
        Promise.resolve(6),
        Promise.resolve(7)
    ]);
    setTimeout(console.log, 0, p3); // Promise <resolved>: 5

期约的实例方法

期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果。

Promise.prototype.then()

Promise.prototype.then() 是为期约实例添加处理程序的主要方法。接收2个参数:onResolved ,onRejected 处理程序;传给then() 的任何非函数类型的参数都会被静默忽略。如果只想提供onRejected 参数,则在onResolved 参数位置传入undefined。 避免在内存中创建多余的对象。

Promise.prototype.then() 方法返回一个新的期约实例:

    let p1 = new Promise(() => {});  
    let p2 = p1.then(); 
    setTimeout(console.log, 0, p1);           // Promise <pending>    
    setTimeout(console.log, 0, p2);           // Promise <pending>  
    setTimeout(console.log, 0, p1 === p2);    // false

Promise.prototype.catch()

romise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数:onRejected处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用Promise.prototype. then(null, onRejected)。

    let p = Promise.reject();    
    let onRejected = function(e) {     
        setTimeout(console.log, 0, 'rejected');   
    };   
    // 这两种添加拒绝处理程序的方式是一样的:  
    p.then(null, onRejected);   // rejected   
    p.catch(onRejected);        //rejected

Promise.prototype.catch()返回一个新的期约实例:

    let p1 = new Promise(() => {});  
    letp2=p1.catch();   
    setTimeout(console.log, 0, p1);          // Promise <pending>  
    setTimeout(console.log, 0, p2);          //Promise<pending>  
    setTimeout(console.log, 0, p1===p2);     //false

拒绝错误处理:then() 和catch() 的onRejected 处理程序在语义上相当于try/catch。 onRejected 处理程序的任务应该是在捕获异步错误之后返回一个==解决(resolve)==的期约;

  new Promise((resolve, reject) => {
        console.log('begin asynchronous execution');
        reject(Error('bar'));
    })
        .catch((e) => { console.log('caught error', e); })  // 捕获异常后,返回一个resolve的期约
        .then(() => { console.log('continue asynchronous execution'); })
    // begin asynchronous execution
    // caught error Error: bar
    // continue asynchronous execution

Promise.prototype.finally()

期约转换为解决或拒绝都会执行,finally 处理程序不知道状态时解决还是拒绝,所以这个方法主要用于添加清理代码;

    let p1 = Promise.resolve();    
    let p2 = Promise.reject();    
    let onFinally = function() {   
        setTimeout(console.log, 0, 'Finally! ')  
    }   
    p1.finally(onFinally); // Finally   
    p2.finally(onFinally); // Finally

Promise.prototype.finally() 方法返回一个新的期约实例,这个新期约实例不同于then() 或catch() 方法返回的实例。因为finally() 被设计为一个状态无关的方法,所以在大多数情况下它将表现为父期约的传递。对于已解决状态和被拒绝状态都是如此。

    let p1 = Promise.resolve('foo');    
    // 这里都会原样后传   
    let p2 = p1.finally();    
    let p3 = p1.finally(() => undefined); 
    let p4 = p1.finally(() => {});   
    let p5 = p1.finally(() => Promise.resolve()); 
    let p6 = p1.finally(() => 'bar');    
    let p7 = p1.finally(() => Promise.resolve('bar')); 
    let p8 = p1.finally(() => Error('qux')); 
    setTimeout(console.log, 0, p2);   // Promise <resolved>: foo   
    setTimeout(console.log, 0, p3);   // Promise <resolved>: foo  
    setTimeout(console.log, 0, p4);   // Promise <resolved>: foo  
    setTimeout(console.log, 0, p5);   // Promise <resolved>: foo    
    setTimeout(console.log, 0, p6);   // Promise <resolved>: foo    
    setTimeout(console.log, 0, p7);   // Promise <resolved>: foo   
    setTimeout(console.log, 0, p8);   // Promise <resolved>: foo

期约扩展

期约取消

通过let id = setTimeout 开启任务,当取消的时候通过setTimeout的id 去取消任务clearTimeout(id);

    <button id="start">Start</button> 
    <button id="cancel">Cancel</button>    
    <script>    
    class CancelToken {
        constructor(cancelFn) {
            this.promise = new Promise((resolve, reject) => {
                cancelFn(() => {
                    setTimeout(console.log, 0, "delay cancelled");
                    resolve();
                });
            });
        }
    }
    const startButton = document.querySelector('#start');
    const cancelButton = document.querySelector('#cancel');
    function cancellableDelayedResolve(delay) {
        setTimeout(console.log, 0, "set delay");
        return new Promise((resolve, reject) => {
            const id = setTimeout((() => {
                setTimeout(console.log, 0, "delayed resolve");
                resolve();
            }), delay);
            const cancelToken = new CancelToken((cancelCallback) =>
                cancelButton.addEventListener("click", cancelCallback));
            cancelToken.promise.then(() => clearTimeout(id));
        });
    }
    startButton.addEventListener("click", () => cancellableDelayedResolve(1000));    
    </script>

小结

长期以来,掌握单线程JavaScript运行时的异步行为一直都是个艰巨的任务。随着ES6新增了期约和ES8新增了异步函数,ECMAScript的异步编程特性有了长足的进步。通过期约和async/await,不仅可以实现之前难以实现或不可能实现的任务,而且也能写出更清晰、简洁,并且容易理解、调试的代码。

期约的主要功能是为异步代码提供了清晰的抽象。可以用期约表示异步执行的代码块,也可以用期约表示异步计算的值。在需要串行异步代码时,期约的价值最为突出。作为可塑性极强的一种结构,期约可以被序列化、连锁使用、复合、扩展和重组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值