澳门新浦京娱乐游戏ES6 Promise

澳门新浦京娱乐游戏 14

在阅读本文之前,你应该已经了解JavaScript异步实现的几种方式:回调函数,发布订阅模式,Promise,生成器(Generator),其实还有async/await方式,这个后续有机会会介绍。本篇将介绍Promise,读完你应该了解什么是Promise,为什么使用Promise,而不是回调函数,Promise怎么使用,使用Promise需要注意什么,以及Promise的简单实现。

时间: 2019-11-02阅读: 105标签: Promise引语

Promise是异步编程的一种解决方案,从语法上说,Promise是一个对象,从它可以获取异步操作的消息。

前言

如果你已经对JavaScript异步有一定了解,或者已经阅读过本系列的其他两篇文章,那请继续阅读下一小节,若你还有疑惑或者想了解JavaScript异步机制与编程,可以阅读一遍这两篇文章:

  • 澳门新浦京娱乐游戏,JavaScript之异步编程简述
  • JavaScript之异步编程

最近一段时间在重温ES6,Promise应该是是ES6新特性中非常重要的一部分内容。其实Promise在我日常开发中已经用得比较多,但大多数时候只是知道Promise可以用来实现异步编程,也只限于单纯地会用罢了,并没有时间深入去学习过,而且网上得资料大多都比较琐碎。我就自己花时间做了一个关于Promise比较完整的整理,深入学习一下Promise,加深印象。

Promise的基本用法

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供。

  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
  • then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为
    Reject时调用。

    var promise = new Promise(

    //异步执行,Promise对象创建后会被立即执行
    function (resolve,reject) {
        //耗时很长的异步操作
    

      if(‘异步处理成功’) {  

        resolve();    //数据处理成功时调用
    

      } else {

        reject();    //数据处理失败时调用
    }
    
    }
    

    )
    //Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
    promise.then(

    function A() {
        //数据处理成功后执行
    },
    function B() {
        //数据处理失败后执行
    }
    

    )

 

下面我们举一个简单的例子来模拟一下异步操作成功和异步操作失败函数的运行过程。

 

console.log('starting');
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒后,我运行了");
        resolve('异步操作成功了');     //1
        //reject('异步操作失败了');    //2
     //return 'hello';       //3

    }, 2000) 

}).then(function (value) { 
    console.log('异步操作成功后执行我:',value);
},
function (value) {
    console.log('异步操作失败后执行我:',value);
}
)
console.log('我也运行了');

// 上面的代码中1处代码的调用,输出顺序是:
//starting
//我也运行了
//2秒后,我运行了
// 异步操作成功后执行我: 异步操作成功了


// 上面的代码中2处代码的调用,输出顺序是:
//starting
//我也运行了
//2秒后,我运行了
// 异步操作失败后后执行我: 异步操作失败了


//上面的代码中3处代码的调用,输出顺序是:
//starting
//我也运行了
//2秒后,我运行了

知代码3处的return 'hello' 语句在新建的new Promise对象中并没有被当作参数返回给then()函数内.那么会不会返回给promise了呢?我们用一段代码来测试一下

console.log('starting');
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒后,我运行了");
        resolve('异步操作成功了');     //1
        //reject('异步操作失败了');    //2
        return 'hello';
    }, 2000) 

})
promise.then(function (value) { 
    console.log('异步操作成功后执行我:',value);
},
function (value) {
    console.log('异步操作失败后执行我:',value);
}
)
console.log('我也运行了');
console.log(promise);
setTimeout(function () {
    console.log('5秒后,我执行了');
    console.log(promise);
},5000);


//starting
//我也运行了
//Promise { pending }
  //[[PromiseStatus]]:"pending"
  //[[PromiseValue]]:undefined
  //__proto__:Promise {constructor: , then: , catch: , …}
//2秒后,我运行了
//异步操作成功后执行我: 异步操作成功了
//5秒后,我执行了
//Promise { resolved }
  //[[PromiseStatus]]:"resolved"
  //[[PromiseValue]]:"异步操作成功了"
  //__proto__:Promise {constructor: , then: , catch: , …}

由执行结果可知,变量promise仍然是new
Promise对象的一个实例。所以return语句虽然被执行了,但对promise实例不会产生任何影响,相当于不存在。

由上面测试的代码可知,Promise对象有以下两个特点。
  (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)
和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,

  (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变
为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

回调函数

回调函数,作为JavaScript异步编程的基本单元,非常常见,你肯定对下面这类代码一点都不陌生:

    component.do('purchase', funcA);
    function funcA(args, callback) {
        //...
        setTimeout(function() {
            $.ajax(url, function(res) {
                if (res) {
                    callback(res)
                } else {//...}
            });
        }, 300);
        funcB();
        setTimeout(function() {
            $.ajax(arg, function(res) {
                if (res) {
                    callback(res);
                }
            });
        }, 400);
    }

上面这些代码,一层一层,嵌套在一起,这种代码通常称为回调地狱,无论是可读性,还是代码顺序,或者回调是否可信任,亦或是异常处理角度看,都是不尽人意的,下面做简单阐述。

为什么需要Promise

resolve(value) VS resolve(Promise)

我们会在异步操作成功时调用resolve函数,其作用是将Promise对象的状态从Pending变为Resolved,并将异步操作的结果作为参数传递给then()方法里的第一个函数的形参。

那么传入的参数是值和传入的参数是promise对象时有什么不同呢。我们来看一下例子。

当传入的参数为值时:

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("1秒后,我运行了");
        resolve('异步操作成功了');     //1
    }, 2000) 

}).then(function (value) {
    console.log(value,new Date() - time);
})
//执行的输出结果为:
//2秒后,我运行了
//异步操作成功了 1002

大约过了一秒左右,我们可以看到在resolved状态的回调方法中,我们打印出了上面注释中的内容。我们能够通过resolve方法传递操作的结果,然后在回调方法中使用这些结果。

如果我们在resolve中传入一个Promise实例呢?

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒后,我运行了");
        resolve('异步操作成功了');     //1
    }, 2000) 

})

var promise2 = new Promise(function (resolve,reject) {
    setTimeout(resolve,1000,promise);//setTimeout和setInterval中的第三个、及以后的参数会作为第一个参数func的参数传递给func函数
}).then(function (value) {
    console.log(value,new Date() - time);
})

//执行后输出的结果为:
//2秒后,我运行了
//异步操作成功了 2003

promise2经过了2秒后才打印出来结果。奇怪了,我们不是设置promise2经过1秒后执行吗?

简单说就是因为promise2中的resolve()函数传入了promise对象,此时promise对象的状态决定了promise的状态,同时会把返回值传给promise。

Promise/A+中规定 [[Resolve]](promise, x)

2.3.2.如果x是一个promise实例, 则以x的状态作为promise的状态

  2.3.2.1.如果x的状态为pending, 那么promise的状态也为pending,
直到x的状态变化而变化。

  2.3.2.2.如果x的状态为fulfilled, promise的状态也为fulfilled,
并且以x的不可变值作为promise的不可变值。

  2.3.2.3.如果x的状态为rejected, promise的状态也为rejected,
并且以x的不可变原因作为promise的不可变原因。

2.3.4.如果x不是对象或函数,则将promise状态转换为fulfilled并且以x作为promise的不可变值。

顺序性

上文例子中代码funcB函数,还有两个定时器回调函数,回调内各自又有一个ajax异步请求然后在请求回调里面执行最外层传入的回调函数,对于这类代码,你是否能明确指出个回调的执行顺序呢?如果funcB函数内还有异步任务呢?,情况又如何?

假如某一天,比如几个月后,线上出了问题,我们需要跟踪异步流,找出问题所在,而跟踪这类异步流,不仅需要理清个异步任务执行顺序,还需要在众多回调函数中不断地跳跃,调试(或许你还能记得诸如funcB这些函数的作用和实现),无论是出于效率,可读性,还是出于人性化,都不希望开开发者们再经历这种痛苦。

JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript
在设计之初是用于浏览器的 GUI 编程,这也就需要线程不能进行阻塞。

 Promise.prototype.then()

Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。
前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒后,我运行了");
        resolve('异步操作成功了');     //1
    }, 2000) 

})
promise.name = 'promise';
console.log('promise:',promise)
var promise2 = promise.then(function (value) {
    console.log(value);
})
promise2.name = 'promise2';
console.log('promise2:',promise2);

// promise:
//     Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise"
//     __proto__:Promise {constructor: , then: , catch: , …}
// promise2:
// Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise2"
//     __proto__:Promise {constructor: , then: , catch: , …}

 

我们可以知道promise.then()方法执行后返回的是一个新的Promise对象。也就是说上面代码中的promise2是一个Promise对象,它的实现效果和下面的代码是一样的,只不过在then()方法里,JS引擎已经自动帮我们做了。

 

promise2 = new Promise(function (resolve,reject) {})

 

既然在then()函数里已经自动帮我实现了一个promise对象,但是我要怎么才能给resolve()或reject()函数传参呢?其实在then()函数里,我们可以用return()的方式来给promise2的resolve()或reject()传参。看一个例子。

//var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒后,我运行了");
        resolve('异步操作成功了');     //1
        //reject('异步操作失败了');    //2
    }, 2000) 

})
//console.log('promise:',promise)
var promise2 = promise.then(function (value) {
    console.log(value);
    //resolve('nihao');  //报错。注意,这里不能写resolve()方法,因为在then函数里是没有resolve方法的
    return (1);       
    //return promise;   //也可以return一个promise对象,返回promise对象执行后resolve('参数值')或reject('参数值')内部的参数值
  //如果不写return的话,默认返回的是return undefined 。
})
var promise3 = promise2.then(function (value) {
    console.log('is:',value);
},function (value) {
    console.log('error:',value);
})
promise2.name = 'promise2';
setTimeout(function () {
    console.log(promise2);
},3000);


//2秒后,我运行了
//异步操作成功了
//is: 1
//Promise {resolved}
    //name:"promise2"
    //__proto__:Promise
    //[[PromiseStatus]]:"resolved"
    //[[PromiseValue]]:1

信任问题

如上,我们调用了一个第三方支付组件的支付API,进行购买支付,正常情况发现一切运行良好,但是假如某一天,第三方组件出问题了,可能多次调用传入的回调,也可能传回错误的数据。说到底,这样的回调嵌套,控制权在第三方,对于回调函数的调用方式、时间、次数、顺序,回调函数参数,还有下一节将要介绍的异常和错误都是不可控的,因为无论如何,并不总能保证第三方是可信任的。

所以在后续的发展过程中基本都采用异步非阻塞的编程模式。

Promise与错误状态处理

.then(null, rejection),用于指定异步操作发生错误时执行的回调函数。下面我们做一个示例。

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject('error');
    },2000);
}).then(null,function(error) {
    console.log('rejected', error)
});
//rejected error

我们知道then()方法执行后返回的也是一个promise对象,因此也可以调用then()方法,但这样的话为了捕获异常信息,我们就需要为每一个then()方法绑定一个.then(null,
rejection)。由于Promise对象的错误信息具有“冒泡”性质,错误会一直向后传递,直到被捕获为止(向后传递指的是:后面的then函数会一直执行rejected下的代码,并抛出第一个then函数/Promise执行出错时的信息)。因此Promise为我们提供了一个原型上的函数Promise.prototype.catch()来让我们更方便的捕获到异常。

我们看一个例子

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject('error');
    },2000);
}).then(function(value) {
    console.log('resolve', value);
}).catch(function (error) {
    console.log(error);
})
//运行结果
//error

上面代码中,一共有二个Promise对象:一个由promise产生,一个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。

但是如果用.then(null,
rejection)方法来处理错误信息,我们需要在每一个rejection()方法中返回上一次异常信息的状态,这样当调用的then()方法一多的时候,对会对代码的清晰性和逻辑性造成影响。

所以,一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。

 

错误处理

关于JavaScript错误异常,初中级开发接触的可能并不多,但是其实还是有很多可以学习实践的地方,如前端异常监控系统的设计,开发和部署,并不是三言两语能阐述的,之后会继续推出相关文章。

简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。

错误堆栈

我们知道当JavaScript抛出错误或异常时,对于未捕获异常,浏览器会默认在控制台输出错误堆栈信息,如下,当test未定义时:

   function init(name) {
        test(name)
    }

    init('jh');

输出如图:

澳门新浦京娱乐游戏 1

如图中自顶向下输出红色异常堆栈信息,Uncaught表示该异常未捕获,ReferenceError表明该异常类型为引用异常,冒号后是异常的详细信息:test is not definedtest未定义;后面以at起始的行就是该异常发生处的调用堆栈。第一行说明异常发生在init函数,第二行说明init函数的调用环境,此处在控制台直接调用,即相当于在匿名函数环境内调用。

异步错误堆栈

上面例子是同步代码执行的异常,当异常发生在异步任务内时,又会如何呢?,假如把上例中代码放在一个setTimeout定时器内执行:

    function init(name) {
        test(name)
    }
    setTimeout(function A() {
        setTimeout(function() {
            init();
        }, 0);
    }, 0);

如图:

澳门新浦京娱乐游戏 2

可以看到,异步任务中的未捕获异常,也会在控制台输出,但是setTimeout异步任务回调函数没有出现在异常堆栈,为什么呢?这是因为当init函数执行时,setTimeout的异步回调函数不在执行栈内,而是通过事件队列调用。

JavaScript错误处理

JavaScript的异常捕获,主要有两种方式:

  • try{}catch(e){}主动捕获异常;澳门新浦京娱乐游戏 3如上,对于同步执行大代码出现异常,try{}catch(e){}是可以捕获的,那么异步错误呢?澳门新浦京娱乐游戏 4如上图,我们发现,异步回调中的异常无法被主动捕获,由浏览器默认处理,输出错误信息。
  • window.onerror事件处理器,所有未捕获异常都会自动进入此事件回调澳门新浦京娱乐游戏 5如上图,输出了script error错误信息,同时,你也许注意到了,控制台依然打印出了错误堆栈信
    息,或许你不希望用户看到这么醒目的错误提醒,那么可以使window.onerror的回调返回true即可阻止浏览器的默认错误处理行为:澳门新浦京娱乐游戏 6当然,一般不随意设置window.onerror回调,因为程序通常可能需要部署前端异常监控系统,而通常就是使用window.onerror处理器实现全局异常监控,而该事件处理器只能注册一个回调。

也正是因为这样,我们常常会说: JavaScript 是由事件驱动的。

回调与PROMISE

以上我们谈到的诸多关于回调的不足,都很常见,所以必须是需要解决的,而Promise正是一种很好的解决这些问题的方式,当然,现在已经提出了比Promise更先进的异步任务处理方式,但是目前更大范围使用,兼容性更好的方式还是Promise,也是本篇要介绍的,之后会继续介绍其他处理方式。

用 JavaScript 构建一个应用的时候经常会遇到异步编程,不管是 Node
服务端还是 Web 前端。

Promises/A+

分析了一大波问题后,我们知道Promise的目标是异步管理,那么Promise到底是什么呢?

  • 异步,表示在将来某一时刻执行,那么Promise也必须可以表示一个将来值;
  • 异步任务,可能成功也可能失败,则Promise需要能完成事件,标记其状态值(这个过程即决议-resolve,下文将详细介绍);
  • 可能存在多重异步任务,即异步任务回调中有异步任务,所以Promise还需要支持可重复使用,添加异步任务(表现为顺序链式调用,注册异步任务,这些异步任务将按注册的顺序执行)。

所以,Promise是一种封装未来值的易于复用的异步任务管理机制。

为了更好的理解Promise,我们介绍一下Promises/A+,一个公开的可操作的Promises实现标准。先介绍标准规范,再去分析具体实现,更有益于理解。

Promise代表一个异步计算的最终结果。使用promise最基础的方式是使用它的then方法,该方法会注册两个回调函数,一个接收promise完成的最终值,一个接收promise被拒绝的原因。

那如何去进行异步编程呢?回调函数就是异步执行最基础的实现。如果你曾经写过一点的
Node, 可能经常会遇到这样的代码:

PROMISES/A

你可能还会想问Promises/A是什么,和Promises/A+有什么区别。Promises/A+在Promises/A议案的基础上,更清晰阐述了一些准则,拓展覆盖了一些事实上的行为规范,同时删除了一些不足或者有问题的部分。

Promises/A+规范目前只关注如何提供一个可操作的then方法,而关于如何创建,决议promises是日后的工作。

connection.query(sql, (err, result) = { if(err) { console.err(err) } else { connection.query(sql, (err, result) = { if(err) { console.err(err) } else { … } }) }})

术语

  1. promise: 指一个拥有符合规范的then方法的对象;
  2. thenable: 指一个定义了then方法的对象;
  3. 决议(resolve): 改变一个promise等待状态至已完成或被拒绝状态,
    一旦决议,不再可变;
  4. 值(value):
    一个任意合法的JavaScript值,包括undefined,thenable对象,promise对象;
  5. exception/error: JavaScript引擎抛出的异常/错误
  6. 拒绝原因(reject reason): 一个promise被拒绝的原因

如此,connection.query()
是一个异步的操作,我们在调用他的时候,不会马上得到结果,而是会继续执行后面的代码。这样,如果我们需要在查到结果之后才做某些事情的话,就需要把相关的代码写在回调里面。

PROMISE状态

一个promise只可能处于三种状态之一:

  • 等待(pending):初始状态;
  • 已完成(fulfilled):操作成功完成;
  • 被拒绝(rejected):操作失败;

这三个状态变更关系需满足以下三个条件:

  • 处于等待(pending)状态时,可以转变为已完成(fulfilled)或者被拒绝状态(rejected);
  • 处于已完成状态时,状态不可变,且需要有一个最终值;
  • 处于被拒绝状态时,状态不可变,且需要有一个拒绝原因。

这样的代码看层级少了当然还是可以凑合看的,但是在某些场景下,我们就需要一次又一次的回调…回调到最后,会发现我们的代码就会变成金字塔形状?这种情况被亲切地称为回调地狱

THEN方法

一个promise必须提供一个then方法,以供访问其当前状态,或最终值或拒绝原因。

参数

该方法接收两个参数,如promise.then(onFulfilled, onRejected):

  • 两个参数均为可选,均有默认值,若不传入,则会使用默认值;
  • 两个参数必须是函数,否则会被忽略,使用默认函数;
  • onFulfilled:
    在promise已完成后调用且仅调用一次该方法,该方法接受promise最终值作参数;
  • onRejected:
    在promise被拒绝后调用且仅调用一次该方法,该方法接受promise拒绝原因作参数;
  • 两个函数都是异步事件的回调,符合JavaScript事件循环处理流程

返回值

该方法必须返回一个promise:

    var promise2 = promise1.then(onFulfilled, onRejected);
    // promise2依然是一个promise对象

回调地狱不仅看起来很不舒服,可读性比较差,难以维护,除此之外还有比较重要的一点就是对异常的捕获无法支持。无论是开发者自身还是同事来接手项目,都是极其无奈的!

决议过程(RESOLUTION)

决议是一个抽象操作过程,该操作接受两个输入:一个promise和一个值,可以记为;[[resolve]](promise, x),如果x是一个thenable对象,则尝试让promise参数使用x的状态值;否则,将使用x值完成传入的promise,决议过程规则如下:

  1. 如果promisex引用自同一对象,则使用一个TypeError原因拒绝此promise;
  2. x为Promise,则promise直接使用x的状态;
  3. x为对象或函数:
    1. 获取一个x.then的引用;
    2. 若获取x.then时抛出异常e,使用该e作为原因拒绝promise;
    3. 否则将该引用赋值给then;
    4. then是一个函数,就调用该函数,其作用域为x,并传递两个回调函数参数,第一个是resolvePromise,第二个是rejectPromise
      1. 若调用了resolvePromise(y),则执行resolve(promise, y);
      2. 若调用了rejectPrtomise(r),则使用原因r拒绝promise;
      3. 若多次调用,只会执行第一次调用流程,后续调用将被忽略;
      4. 若调用then抛出异常e,则:
        1. promise已决议,即调用了resolvePromiserejectPrtomise,则忽略此异常;
        2. 否则,使用原因e拒绝promise;
    5. then不是函数,则使用x值完成promise;
  4. x不是对象或函数,则使用x完成promise

自然,以上规则可能存在递归循环调用的情况,如一个promsie被一个循环的thenable对象链决议,此时自然是不行的,所以规范建议进行检测,是否存在递归调用,若存在,则以原因TypeError拒绝promise

有没有更好的写法?比如写成这样的链式调用:

Promise

在ES6中,JavaScript已支持Promise,一些主流浏览器也已支持该Promise功能,如Chrome,先来看一个Promsie使用实例:

    var promise = new Promise((resolve, reject) => {
        setTimeout(function() {
            resolve('完成');
        }, 10);
    });
    promise.then((msg) => {
        console.log('first messaeg: ' + msg);
    })
    promise.then((msg) => {
        console.log('second messaeg: ' + msg);
    });

输出如下:

澳门新浦京娱乐游戏 7

let con = connection.query(…con.success(…) .fail(…)

构造器

创建promise语法如下:

   new Promise(function(resolve, reject) {});
  • 参数一个函数,该函数接受两个参数:resolve函数和reject函数;当实例化Promise构造函数时,将立即调用该函数,随后返回一个Promise对象。通常,实例化时,会初始一个异步任务,在异步任务完成或失败时,调用resolve或reject函数来完成或拒绝返回的Promise对象。另外需要注意的是,若传入的函数执行抛出异常,那么这个promsie将被拒绝。

于是乎,出现了Promise!

静态方法

Promise.all(iterable)

all方法接受一个或多个promsie(以数组方式传递),返回一个新promise,该promise状态取决于传入的参数中的所有promsie的状态:

  1. 当所有promise都完成是,返回的promise完成,其最终值为由所有完成promsie的最终值组成的数组;
  2. 当某一promise被拒绝时,则返回的promise被拒绝,其拒绝原因为第一个被拒绝promise的拒绝原因;

    var p1 = new Promise((resolve, reject) => {
        setTimeout(function(){
            console.log('p1决议');
            resolve('p1');
        }, 10);
    });
    var p2 = new Promise((resolve, reject) => {
        setTimeout(function(){
            console.log('p2决议');
            resolve('p2');
        }, 10);
    });
    Promise.all( [p1, p2] )
    .then((msgs) => {
        // p1和p2完成并传入最终值
        console.log(JSON.stringify(msgs));
    })
    .then((msg) => {
        console.log( msg );
    });

输出如下:

澳门新浦京娱乐游戏 8

Promise.race(iterable)

race方法返回一个promise,只要传入的诸多promise中的某一个完成或被拒绝,则该promise同样完成或被拒绝,最终值或拒绝原因也与之相同。

Promise.resolve(x)

resolve方法返回一个已决议的Promsie对象:

  1. x是一个promise或thenable对象,则返回的promise对象状态同x;
  2. x不是对象或函数,则返回的promise对象以该值为完成最终值;
  3. 否则,详细过程依然按前文Promsies/A+规范中提到的规则进行。

该方法遵循Promise/A+决议规范。

Promsie.reject(reason)

返回一个使用传入的原因拒绝的Promise对象。

什么是Promise?Promise的含义

实例方法

Promise.prototype.then(onFulfilled, onRejected)

该方法为promsie添加完成或拒绝处理器,将返回一个新的promise,该新promise接受传入的处理器调用后的返回值进行决议;若promise未被处理,如传入的处理器不是函数,则新promise维持原来promise的状态。

我们通过两个例子介绍then方法,首先看第一个实例:

    var promise = new Promise((resolve, reject) => {
        setTimeout(function() {
            resolve('完成');
        }, 10);
    });
    promise.then((msg) => {
        console.log('first messaeg: ' + msg);
    }).then((msg) => {
        console.log('second messaeg: ' + msg);
    });

输出如下:

澳门新浦京娱乐游戏 9

输出两行信息:我们发现第二个then方法接收到的最终值是undefined,为什么呢?看看第一个then方法调用后返回的promise状态如下:

澳门新浦京娱乐游戏 10

如上图,发现调用第一个then方法后,返回promise最终值为undefined,传递给第二个then的回调,如果把上面的例子稍加改动:

    var promise = new Promise((resolve, reject) => {
        setTimeout(function() {
            resolve('完成');
        }, 10);
    });
    promise.then((msg) => {
        console.log('first messaeg: ' + msg);
        return msg + '第二次';
    }).then((msg) => {
        console.log('second messaeg: ' + msg);
    });

输出如下:

澳门新浦京娱乐游戏 11

这次两个then方法的回调都接收到了最终值,正如我们前文所说,’then’方法返回一个新promise,并且该新promise根据其传入的回调执行的返回值,进行决议,而函数未明确return返回值时,默认返回的是undefined,这也是上面实例第二个then方法的回调接收undefined参数的原因。

这里使用了链式调用,我们需要明确:共产生三个promise,初始promise,两个then方法分别返回一个promise;而第一个then方法返回的新promise是第二个then方法的主体,而不是初始promise。

Promise.prototype.catch(onRejected)

该方法为promise添加拒绝回调函数,将返回一个新promise,该新promise根据回调函数执行的返回值进行决议;若promise决议为完成状态,则新promise根据其最终值进行决议。

    var promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('failed');
        }, 0);
    });

    var promise2 = promise.catch((reason) => {
        console.log(reason);
        return 'successed';
    });
    var promise3 = promise.catch((reason) => {
        console.log(reason);
    });
    var promise4 = promise.catch((reason) => {
        console.log(reason);
        throw 'failed 2';
    });

输出如下图:

澳门新浦京娱乐游戏 12

如图中所输出内容,我们需要明白以下几点:

  1. catch会为promise注册拒绝回调函数,一旦异步操作结束,调用了reject回调函数,则依次执行注册的拒绝回调;
  2. 另外有一点和then方法相似,catch方法返回的新promise将使用其回调函数执行的返回值进行决议,如promise2,promise3状态均为完成(resolved),但是promise3最终值为undefined,而promise2最终值为successed,这是因为在调用promise.catch方法时,传入的回调没有显式的设置返回值;
  3. 对于promise4,由于调用catch方法时,回调中throw抛出异常,所以promise4状态为拒绝(rejected),拒绝原因为抛出的异常;
  4. 特别需要注意的是这里一共有四个promise,一旦决议,它们之间都是独立的,我们需要明白无论是then方法,还是catch方法,都会返回一个新promise,此新promise与初始promise相互独立。

catch方法和then方法的第二个参数一样,都是为promise注册拒绝回调。

Promise
是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6
将其写进了语言标准,统一了用法,原生提供了Promise对象。

链式调用

和jQuery的链式调用一样,Promise设计也支持链式调用,上一步的返回值作为下一步方法调用的主体:

   new Promise((resolve, reject) => {
        setTimeout(()=>{
            resolve('success');
        },0);
    }).then((msg) => {
        return 'second success';
    }).then((msg) => {
        console.log(msg);
    });

最后输出:second success,初始化promise作为主体调用第一个then方法,返回完成状态的新promise其最终值为second success,然后该新promise作为主体调用第二个then方法,该方法返回第三个promise,而且该promise最终值为undefined,若不清楚为什么,请回到关于Promise.prototype.thenPromise.prototype.catch的介绍。

古人云:“君子一诺千金”,所谓Promise,正如其中文含义,简单说就是一个承诺,“承诺将来会执行”约定的事情。

错误处理

我们前文提到了JavaScript异步回调中的异常是难以处理的,而Promise对异步异常和错误的处理是比较方便的:

   var promise = new Promise((resolve, reject) => {
        test(); // 抛出异常
        resolve('success'); // 被忽略
    });
    console.log(promise);
    promise.catch((reason) => {
        console.log(reason);
    });

输出如图,执行test抛出异常,导致promise被拒绝,拒绝原因即抛出的异常,然后执行catch方法注册的拒绝回调:

澳门新浦京娱乐游戏 13

从语法上说,Promise
是一个对象,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从它可以获取异步操作的消息。Promise
提供统一的 API,各种异步操作都可以用同样的方法进行处理。

决议,完成与拒绝

目前为止,关于Promise是什么,我们应该有了一定的认识,这里,需要再次说明的是Promise的三个重要概念及其关系:决议(resolve),完成(fulfill),拒绝(reject)。

  1. 完成与拒绝是Promise可能处于的两种状态;
  2. 决议是一个过程,是Promise由等待状态变更为完成或拒绝状态的一个过程;
  3. 静态方法Promise.resolve描述的就是一个决议过程,而Promise构造函数,传入的回调函数的两个参数:resolve和reject,一个是完成函数,一个是拒绝函数,这里令人疑惑的是为什么这里依然使用resolve而不是fulfill,我们通过一个例子解释这个问题:

   var promise = new Promise((resolve, reject) => {
        resolve(Promise.reject('failed'));
    });
    promise.then((msg) => {
        console.log('完成:' + msg);
    }, (reason) => {
        console.log('拒绝:' + reason);
    });

输出如图:

澳门新浦京娱乐游戏 14

上例中,在创建一个Promise时,给resolve函数传递的是一个拒绝Promise,此时我们发现promise状态是rejected,所以这里第一个参数函数执行,完成的是一个更接近决议的过程(可以参考前文讲述的决议过程),所以命名为resolve是更合理的;而第二个参数函数,则只是拒绝该promise:

    var promise = new Promise((resolve, reject) => {
        reject(Promise.resolve('success'));
    });
    promise.then((msg) => {
        console.log('完成:' + msg);
    }, (reason) => {
        console.log('拒绝:' + reason);
    });

reject函数并不会处理参数,而只是直接将其当做拒绝原因拒绝promise。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise实现

Promise是什么,怎么样使用就介绍到此,另外一个问题是面试过程中经常也会被提及的:如何实现一个Promise,当然,限于篇幅,我们这里只讲思路,不会长篇大论。

约定

构造函数

首先创建一个构造函数,供实例化创建promise,该构造函数接受一个函数参数,实例化时,会立即调用该函数,然后返回一个Promise对象:

    var MyPromise = (() => {
        var value = undefined; // 当前Promise
        var tasks = []; // 完成回调队列
        var rejectTasks = []; // 拒绝回调队列
        var state = 'pending'; // Promise初始为等待态

        // 辅助函数,使异步回调下一轮事件循环执行
        var nextTick = (callback) => {
            setTimeout(callback, 0);
        };

        // 辅助函数,传递Promsie的状态值
        var ref = (value) => {
            if (value && typeof value.then === 'function') {
                // 若状态值为thenable对象或Promise,直接返回
                return value;
            }
            // 否则,将最终值传递给下一个then方法注册的回调函数
            return {
                then: function(callback) {
                    return ref(callback(value));
                }
            }
        };
        var resolve = (val) => {};
        var reject = (reason) => {};

        function MyPromise(func) {
            func(resolve.bind(this), reject.bind(this));
        }

        return MyPromise;
    });

不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:

静态方法

在实例化创建Promise时,我们会将构造函数的两个静态方法:resolvereject传入初始函数,接下来需要实现这两个函数:

   var resolve = (val) => {
        if (tasks) {
            value = ref(val);
            state = 'resolved'; // 将状态标记为已完成
            // 依次执行任务回调
            tasks.forEach((task) => {
                value = nextTick((val) => {task[0](self.value);});
            });
            tasks = undefined; // 决议后状态不可变

            return this;
        }
    };
    var reject = (reason) => {
        if (tasks) {
            value = ref(reason);
            state = 'rejected'; // 将状态标记为已完成

            // 依次执行任务回调
            tasks.forEach((task) => {
                nextTick((reason) => {task[1](value);});
            });
            tasks = undefined; // 决议后状态不可变

            return this;
        }
    };

还有另外两个静态方法,原理还是一样,就不细说了。

在本轮 Javascript event
loop(事件循环)运行完成之前,回调函数是不会被调用的。通过then()添加的回调函数总会被调用,即便它是在异步操作完成之后才被添加的函数。通过多次调用then(),可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行。

实例方法

目前构造函数,和静态方法完成和拒绝Promise都已经实现,接下来需要考虑的是Promise的实例方法和链式调用:

    MyPromise.prototype.then = (onFulfilled, onRejected) => {
        onFulfilled = onFulfilled || function(value) {
            // 默认的完成回调
            return value;
        };
        onRejected = onRejected || function(reason) {
            // 默认的拒绝回调
            return reject(reason);
        };

        if (tasks) {
            // 未决议时加入队列
             tasks.push(onFulfilled);
             rejectTasks.push(onRejected);
        } else {
            // 已决议,直接加入事件循环执行
             nextTick(() => {
                 if (state === 'resolved') {
                     value.then(onFulfilled);
                 } else if (state === 'rejected') {
                     value.then(onRejected);
                 }
             });
        }

        return this;
    };

因此,Promise 最直接的好处就是链式调用chaining)。

实例

以上可以简单实现Promise部分异步管理功能:

    var promise = new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('完成');
        }, 0);
    });
    promise.then((msg) => {console.log(msg);});

本篇由回调函数起,介绍了回调处理异步任务的常见问题,然后介绍Promises/A+规范及Promise使用,最后就Promise实现做了简单阐述(之后有机会会详细实现一个Promise),花费一周终于把基本知识点介绍完,下一篇将介绍JavaScript异步与生成器实现。

Promise的使用一个Promise的三种状态

在开始使用Promise之前,我们首先需要了解Promise的三种状态:

pending: 初始状态,既不是成功,也不是失败状态。fulfilled:
意味着操作成功完成。rejected: 意味着操作失败。

pending 状态的 Promise 对象可能会变为fulfilled
状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise
对象的 then 方法绑定的处理方法(handlers
)就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是
Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled
方法,当Promise状态为rejected时,调用 then 的 onrejected 方法,
所以在异步操作的完成和绑定处理方法之间不存在竞争)。

因为Promise.prototype.thenPromise.prototype.catch方法返回promise
对象, 所以它们可以被链式调用

Promise的这三种状态有两个特点:

对象的状态不受外界影响。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为
resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。Promise基本用法

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) { // … some code If (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由
JavaScript 引擎提供,不用自己部署。

Resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从
pending 变为
resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

Reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从
pending 变为
rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) { // success}, function(error) { // failure});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

下面是一个Promise对象的简单例子。

function timeout(ms) { return new Promise((resolve, reject) = { setTimeout(resolve, ms, 'done'); });}timeout(100).then((value) = { console.log(value);});

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

Promise 新建后就会立即执行

let promise = new Promise(function(resolve, reject) { console.log('1'); resolve();});promise.then(function() { console.log('2');});console.log('3');// = 1// = 3// = 2

上面代码中,Promise
新建后立即执行,所以首先输出的是1。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以2最后输出。

注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。

new Promise((resolve, reject) = { resolve(1); console.log(2);}).then(r = { console.log(r);});// 2// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即
resolved 的 Promise
是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

一般来说,调用resolve或reject以后,Promise
的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) = { return resolve(1); // 后面的语句不会执行 console.log(2);})

下面是异步加载图片的例子。

function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; });}

上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

下面是一个用Promise对象实现的 Ajax 操作的例子。

const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); }); return promise;};getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json);}, function(error) { console.error('出错了', error);});

上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON
数据的 HTTP
请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。

深入Promise原型

对于Promise的基本用法有了一定认识之后,我们来深入学习一下Promise原型方法。

Promise.prototype.then(onFulfilled, onRejected)

上面的例子中已经提到,Promise
实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为
Promise
实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

链式调用

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个Promise
来实现这种需求。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式调用写法,即then方法后面再调用另一个then方法。

const promise = doSomething();const promise2 = promise.then(successCallback, failureCallback);

或者

constpromise2 = doSomething().then(successCallback, failureCallback);

第二个对象(promise2)不仅表示 doSomething()
函数的完成,也代表了你传入的 successCallback 或者 failureCallback
的完成,successCallback 或 failureCallback 有可能返回一个 Promise
对象,从而形成另一个异步操作。这样的话,任何一个 promise2
新增的回调函数,都会被依次排在由上一个successCallback 或 failureCallback
执行后所返回的 Promise 对象的后面。

基本上,每一个 Promise 都代表了链中另一个异步过程的完成。

正如文章开头提到过的,在过去,要想做多重的异步操作,会导致经典的回调地狱:

doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback);}, failureCallback);

通过新的功能方法,我们把回调绑定到被返回的 Promise
上代替以往的做法,形成一个 Promise 链:

doSomething().then(function(result) { return doSomethingElse(result);}).then(function(newResult) { return doThirdThing(newResult);}).then(function(finalResult) { console.log('Got the final result: ' + finalResult);}).catch(failureCallback);

then里的参数是可选的,如下所示,我们也可以用箭头函数来表示:

doSomething().then(result = doSomethingElse(result)).then(newResult = doThirdThing(newResult)).then(finalResult = { console.log(`Got the final result: ${finalResult}`);}).catch(failureCallback);

注意:一定要有返回值,否则,callback 将无法获取上一个 Promise
的结果。(如果使用箭头函数,() = x 比 () = { return x; }
更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。

Promise.prototype.catch(onRejected)

添加一个拒绝(rejection) 回调到当前 promise,
返回一个新的promise。当这个回调函数被调用,新 promise
将以它的返回值来resolve,否则如果当前promise
进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果。

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined,
rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) { // …}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error);});

上面代码中,getJSON方法返回一个 Promise
对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

p.then((val) = console.log('fulfilled:', val)) .catch((err) = console.log('rejected', err));// 等同于p.then((val) = console.log('fulfilled:', val)) .then(null, (err) = console.log("rejected:", err));

下面是一个例子。

const promise = new Promise(function(resolve, reject) { throw new Error('test');});promise.catch(function(error) { console.log(error);});// Error: test

上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。

// 写法一const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); }});promise.catch(function(error) { console.log(error);});

// 写法二const promise = new Promise(function(resolve, reject) { reject(new Error('test'));});promise.catch(function(error) { console.log(error);});

比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

const promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test');});promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) });// ok

上面代码中,Promise
在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise
的状态一旦改变,就永久保持该状态,不会再变了。

Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL);}).then(function(comments) { // some code}).catch(function(error) { // 处理前面三个Promise产生的错误});

上面代码中,一共有三个 Promise
对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。

一般来说,不要在then方法里面定义 Reject
状态的回调函数(即then的第二个参数),总是使用catch方法。

// badpromise .then(function(data) { // success }, function(err) { // error });// goodpromise .then(function(data) { //cb // success }) .catch(function(err) { // error });

上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise
对象抛出的错误不会传递到外层代码,即不会有任何反应。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().then(function() { console.log('everything is great');});setTimeout(() = { console.log(123) }, 2000);// Uncaught (in promise) ReferenceError: x is not defined// 123

上面代码中,someAsyncThing函数产生的 Promise
对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError:
x is not defined,但是不会退出进程、终止脚本执行,2
秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise
外部的代码,通俗的说法就是“Promise 会吃掉错误”。

这个脚本放在服务器执行,退出码就是0(即表示执行成功)。不过,Node
有一个unhandledRejection事件,专门监听未捕获的reject错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。

process.on('unhandledRejection', function (err, p) { throw err;});

上面代码中,unhandledRejection事件的监听函数有两个参数,第一个是错误对象,第二个是报错的
Promise 实例,它可以用来了解发生错误的环境信息。

注意,Node 有计划在未来废除unhandledRejection事件。如果 Promise
内部有未捕获的错误,会直接终止进程,并且进程的退出码不为 0。

再看下面的例子。

const promise = new Promise(function (resolve, reject) { resolve('ok'); setTimeout(function () { throw new Error('test') }, 0)});promise.then(function (value) { console.log(value) });// ok// Uncaught Error: test

上面代码中,Promise
指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise
的运行已经结束了,所以这个错误是在 Promise
函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise
内部发生的错误。catch方法返回的还是一个 Promise
对象,因此后面还可以接着调用then方法。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().catch(function(error) { console.log('oh no', error);}).then(function() { console.log('carry on');});// oh no [ReferenceError: x is not defined]// carry on

上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。

Promise.resolve().catch(function(error) { console.log('oh no', error);}).then(function() { console.log('carry on');});// carry on

上面的代码因为没有报错,跳过了catch方法,直接执行后面的then方法。此时,要是then方法里面报错,就与前面的catch无关了。Catch方法之中,有可能会在一个回调失败之后继续使用链式操作,即使用一个
catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作很有用。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().then(function() { return someOtherAsyncThing();}).catch(function(error) { console.log('oh no', error); // 抛出一个错误 throw new Error('有哪里不对了'); console.log('carry on');});// oh no [ReferenceError: x is not defined]

上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。

someAsyncThing().then(function() { return someOtherAsyncThing();}).catch(function(error) { console.log('oh no', error); // 抛出一个错误 throw new Error('有哪里不对了'); console.log('carry on');}).catch(function(error) { console.log('carry on', error);});// oh no [ReferenceError: x is not defined]// carry on 有哪里不对了

上面代码中,第二个catch方法用来捕获前一个catch方法抛出的错误。

也许你发现了,最后~carry on~并没有被输出,这是因为抛出了错误
~有哪里不对了~

在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在
Promise 链中只有尾部的一次调用。

doSomething().then(result = doSomethingElse(value)).then(newResult = doThirdThing(newResult)).then(finalResult = console.log(`Got the final result: ${finalResult}`)).catch(failureCallback);

通常,一遇到异常抛出,Promise 链就会停下来,直接调用链式中的 catch
处理程序来继续当前执行。这看起来和以下的同步代码的执行很相似。

try { let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Got the final result: ${finalResult}`);} catch(error) { failureCallback(error);}

在 ECMAScript 2017
标准的async/await语法糖中,这种同步形式代码的对称性得到了极致的体现:

async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); }}

这个例子是在 Promise 的基础上构建的,例如,doSomething()
与之前的函数是相同的。

通过捕获所有的错误,甚至抛出异常和程序错误,Promise
解决了回调地狱的基本缺陷。这对于构建异步操作的基础功能而言是很有必要的。

Promise.prototype.finally(onFinally)

添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)。该方法是
ES2018 引入标准的。

promise.then(result = {···}).catch(error = {···}).finally(() = {···});

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

下面是一个例子,服务器使用 Promise
处理请求,然后使用finally方法关掉服务器。

server.listen(port) .then(function () { // … }) .finally(server.stop);

Finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的
Promise
状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于
Promise 的执行结果。

finally本质上是then方法的特例。

promise.finally(() = { // 语句});// 等同于promise.then( result = { // 语句 return result; }, error = { // 语句 throw error; });

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

它的实现也很简单。

Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value = P.resolve(callback()).then(() = value), reason = P.resolve(callback()).then(() = { throw reason }) );};

上面代码中,不管前面的 Promise
是fulfilled还是rejected,都会执行回调函数callback。

从上面的实现还可以看到,finally方法总是会返回原来的值。

// resolve 的值是 undefinedPromise.resolve(2).then(() = {}, () = {})// resolve 的值是 2Promise.resolve(2).finally(() = {})// reject 的值是 undefinedPromise.reject(3).then(() = {}, () = {})// reject 的值是 3Promise.reject(3).finally(() = {})

Promise 拒绝事件

当 Promise
被拒绝时,会有下文所述的两个事件之一被派发到全局作用域(通常而言,就是window;如果是在
web worker 中使用的话,就是 Worker 或者其他 worker-based
接口)。这两个事件如下所示:

rejectionhandled当 Promise 被拒绝、并且在 reject 函数处理该
rejection 之后会派发此事件。unhandledrejection当 Promise
被拒绝,但没有提供 reject 函数来处理该 rejection
时,会派发此事件。以上两种情况中,PromiseRejectionEvent
事件都有两个属性,一个是 promise 属性,该属性指向被驳回的
Promise,另一个是 reason 属性,该属性用来说明 Promise 被驳回的原因。

因此,我们可以通过以上事件为 Promise 失败时提供补偿处理,也有利于调试
Promise
相关的问题。在每一个上下文中,该处理都是全局的,因此不管源码如何,所有的错误都会在同一个handler中被捕捉处理。

一个特别有用的例子:当你使用 Node.js 时,有些依赖模块可能会有未被处理的
rejected
promises,这些都会在运行时打印到控制台。你可以在自己的代码中捕捉这些信息,然后添加与
unhandledrejection 相应的 handler
来做分析和处理,或只是为了让你的输出更整洁。举例如下:

window.addEventListener("unhandledrejection", event = { /* 你可以在这里添加一些代码,以便检查 event.promise 中的 promise 和 event.reason 中的 rejection 原因 */ event.preventDefault();}, false);

调用 event 的preventDefault()方法是为了告诉 JavaScript 引擎当 promise
被拒绝时不要执行默认操作,默认操作一般会包含把错误打印到控制台。

理想情况下,在忽略这些事件之前,我们应该检查所有被拒绝的
Promise,来确认这不是代码中的 bug。

状态传递

如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个
Promise 实例,比如像下面这样。

const p1 = new Promise(function (resolve, reject) { // …});const p2 = new Promise(function (resolve, reject) { // … resolve(p1);})

上面代码中,p1和p2都是 Promise
的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

const p1 = new Promise(function (resolve, reject) { setTimeout(() = reject(new Error('fail')), 3000)})const p2 = new Promise(function (resolve, reject) { setTimeout(() = resolve(p1), 1000)})p2 .then(result = console.log(result)) .catch(error = console.log(error))// = Error: fail

上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1
秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个
Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了
2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

Promise方法Promise.all(iterable)

这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合(可以参考jQuery.when方法)。

这段话有点长,我们举个具体例子来说明:

constp =Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是
Promise
实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为
Promise
实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有
Iterator 接口,且返回的每个成员都是 Promise
实例。P的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。下面是一个具体的例子。

// 生成一个Promise对象的数组const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json");});Promise.all(promises).then(function (posts) { // …}).catch(function(reason){ // …});

上面代码中,promises是包含 6 个 Promise 实例的数组,只有这 6
个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

下面是另一个例子。

const databasePromise = connectDatabase();const booksPromise = databasePromise .then(findAllBooks);const userPromise = databasePromise .then(getCurrentUser);Promise.all([ booksPromise, userPromise]).then(([books, user]) = pickTopRecommendations(books, user));

上面代码中,booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数。

注意,如果作为参数的 Promise
实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) = { resolve('hello');}).then(result = result).catch(e = e);const p2 = new Promise((resolve, reject) = { throw new Error('报错了');}).then(result = result).catch(e = e);Promise.all([p1, p2]).then(result = console.log(result)).catch(e = console.log(e));// ["hello", Error: 报错了]

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的
Promise
实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) = { resolve('hello');}).then(result = result);const p2 = new Promise((resolve, reject) = { throw new Error('报错了');}).then(result = result);Promise.all([p1, p2]).then(result = console.log(result)).catch(e = console.log(e));// Error: 报错了

Promise.race(iterable)

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise
实例。当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

constp =Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的
Promise 实例的返回值,就传递给p的回调函数。

Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise
实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise
实例,再进一步处理。

下面是一个例子,如果指定时间内没有获得结果,就将 Promise
的状态变为reject,否则变为resolve。

const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() = reject(new Error('request timeout')), 5000) })]);p.then(console.log).catch(console.error);

上面代码中,如果 5
秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

Promise.resolve(value)

返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。

通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value)
来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

constjsPromise =Promise.resolve($.ajax('/whatever.json'));

上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。

Promise.resolve()等价于下面的写法。

Promise.resolve('foo')// 等价于new Promise(resolve = resolve('foo'))

现在我们来具体说说Promise.resolve方法的参数分成四种情况。

* 参数是一个 Promise 实例如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。* 参数是一个thenable对象Thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = { then: function(resolve, reject) { resolve(42); }};

Promise.resolve方法会将这个对象转为 Promise
对象,然后就立即执行thenable对象的then方法。

let thenable = { then: function(resolve, reject) { resolve(42); }};let p1 = Promise.resolve(thenable);p1.then(function(value) { console.log(value); // 42});

上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出
42。

* 参数不是具有then方法的对象,或根本就不是对象如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

const p = Promise.resolve('Hello');p.then(function (s){ console.log(s)});// Hello

上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。* 不带有任何参数Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

const p = Promise.resolve();p.then(function () { // ...});

上面代码的变量p就是一个 Promise 对象。需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

setTimeout(function () { console.log('3');}, 0);Promise.resolve().then(function () { console.log('2');});console.log('1');// 1// 2// 3

上面代码中,setTimeout(fn,
0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。

Promise.reject(reason)

返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。

const p = Promise.reject('出错了');// 等同于const p = new Promise((resolve, reject) = reject('出错了'))p.then(null, function (s) { console.log(s)});// 出错了

上面代码生成一个 Promise
对象的实例p,状态为rejected,回调函数会立即执行。

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

const thenable = { then(resolve, reject) { reject('出错了'); }};Promise.reject(thenable).catch(e = { console.log(e === thenable)})// true

上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

Promise.allSettled(iterable)

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的
Promise
实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由ES2020引入。

const promises = [ fetch('/api-1'), fetch('/api-2'), fetch('/api-3'),];await Promise.allSettled(promises);removeLoadingIndicator();

上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。

该方法返回的新的 Promise
实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise
的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的
Promise 实例。

const resolved = Promise.resolve(42);const rejected = Promise.reject(-1);const allSettledPromise = Promise.allSettled([resolved, rejected]);allSettledPromise.then(function (results) { console.log(results);});// [// { status: 'fulfilled', value: 42 },// { status: 'rejected', reason: -1 }// ]

上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个
Promise
实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejected。fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

下面是返回值用法的例子。

const promises = [ fetch('index.html'), fetch('-not-exist/') ];const results = await Promise.allSettled(promises);// 过滤出成功的请求const successfulPromises = results.filter(p = p.status === 'fulfilled');// 过滤出失败的请求,并输出原因const errors = results .filter(p = p.status === 'rejected') .map(p = p.reason);

有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。Promise.all()方法无法做到这一点。

const urls = [ /* ... */ ];const requests = urls.map(x = fetch(x));try { await Promise.all(requests); console.log('所有请求都成功。');} catch { console.log('至少一个请求失败,其他请求可能还没结束。');}

上面代码中,Promise.all()无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了Promise.allSettled(),这就很容易了。

Promise.any(iterable)

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise
实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案

Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。const promises = [ fetch('/endpoint-a').then(() = 'a'), fetch('/endpoint-b').then(() = 'b'), fetch('/endpoint-c').then(() = 'c'),];try { const first = await Promise.any(promises); console.log(first);} catch (error) { console.log(error);}

上面代码中,Promise.any()方法的参数数组包含三个 Promise
操作。其中只要有一个变成fulfilled,Promise.any()返回的 Promise
对象就变成fulfilled。如果所有三个操作都变成rejected,那么就会await命令就会抛出错误。

Promise.any()抛出的错误,不是一个一般的错误,而是一个 AggregateError
实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。下面是
AggregateError 的实现示例。

new AggregateError() extends Array - AggregateErrorconst err = new AggregateError();err.push(new Error("first error"));err.push(new Error("second error"));throw err;

捕捉错误时,如果不用try…catch结构和 await 命令,可以像下面这样写。

Promise.any(promises).then( (first) = { // Any of the promises was fulfilled. }, (error) = { // All of the promises were rejected. });

下面是一个例子。

var resolved = Promise.resolve(42);var rejected = Promise.reject(-1);var alsoRejected = Promise.reject(Infinity);Promise.any([resolved, rejected, alsoRejected]).then(function (result) { console.log(result); // 42});Promise.any([rejected, alsoRejected]).catch(function (results) { console.log(results); // [-1, Infinity]});Promise.resolve()

Promise.try()

实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用
Promise
来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。

Promise.resolve().then(f)

上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。

const f = () = console.log('now');Promise.resolve().then(f);console.log('next');// next// now

上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。

那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的
API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。

const f = () = console.log('now');(async () = f())();console.log('next');// now// next

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

(async () = f())().then(...)

需要注意的是,async () =
f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

(async () = f())().then(...).catch(...)

第二种写法是使用new Promise()。

const f = () = console.log('now');( () = new Promise( resolve = resolve(f()) ))();console.log('next');// now// next

上面代码也是使用立即执行的匿名函数,执行new
Promise()。这种情况下,同步函数也是同步执行的。

鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。

const f = () = console.log('now');Promise.try(f);console.log('next');// now// next

事实上,Promise.try存在已久,Promise
库Bluebird、Q和when,早就提供了这个方法。

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

function getUsername(userId) { return database.users.get({id: userId}) .then(function(user) { return user.name; });}

上面代码中,database.users.get()返回一个 Promise
对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。

database.users.get({id: userId}).then(...).catch(...)

但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try…catch去捕获。

try { database.users.get({id: userId}) .then(…) .catch(…)} catch (e) { // ...}

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

Promise.try(() = database.users.get({id: userId})) .then(...) .catch(...)

事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

应用加载图片

我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。

const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; });};

Generator 函数与 Promise 的结合

使用 Generator
函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。

function getFoo () { return new Promise(function (resolve, reject){ resolve('foo'); });}const g = function* () { try { const foo = yield getFoo(); console.log(foo); } catch (e) { console.log(e); }};function run (generator) { const it = generator(); function go(result) { if (result.done) return result.value; return result.value.then(function (value) { return go(it.next(value)); }, function (error) { return go(it.throw(error)); }); } go(it.next());}run(g);

上面代码的 Generator
函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图