在JavaScript中,回调函数是处理异步编程的常用解决方案,但层层嵌套的回调金字塔(如下代码所示)一直受人诟病,因为不仅在视觉上更加混乱,而且在管理上也更为复杂。

setTimeout(() => {
var reason = "成功执行";
setTimeout(() => {
console.log(reason);
}, 500);
}, 500);

  Promise是ES6新增的特性,能更合理的控制和追踪异步操作。它是一个包含状态、可继承的对象,不仅能管理而不是依赖回调,还能以同步的方式传递异步的计算结果,从而避免陷入回调金字塔的泥潭中。链式(即串联起来)的Promise让代码有更高的可读性和更便捷的调试性。下面会用Promise实现上一个示例的功能。

var promise = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve("成功执行");
}, 500);
});
promise.then(function(value) {
setTimeout(() => {
console.log(value);
}, 500);
});

  示例只是为了能对Promise有个初步的认识,其中涉及的Promise的创建、then()方法等概念,都将在接下来的章节中做详细的讲解。

一、状态

  Promise依据其状态的变化,让异步操作变得有序,而Promise有三种互斥的状态可供选择,具体如下所列。

(1)pending:等待中,初始状态,此时还未处理(Promise中的)异步操作。

(2)fulfilled:已完成,异步操作成功时的状态。

(3)rejected:已拒绝,异步操作失败时的状态。

  每个Promise只能维护一个状态,并且状态只会朝一个方向变化,即从pending变为fulfilled或rejected,而fulfilled不能变为rejected,反之也一样,这种处理状态的行为也叫决议。注意,Promise会在内部处理状态的变化,并且由于ES6对外没有暴露访问Promise状态的属性或方法,因此无法在外部判断Promise当前处在哪个状态。

二、创建

  如果要使用Promise,那么需要先初始化,可以通过构造函数的方式创建一个Promise实例,如下所示。

var promise = new Promise(function(resolve, reject) {
/* executor */
});

  构造函数Promise()能接收一个执行器(executor),即带有resolve和reject两个参数的函数,执行器会在构造函数返回新实例前被调用。它的两个参数也是函数,分别适合不同的语境,具体如下所列。

(1)在执行器中的异步操作完成时会调用resolve()函数,当前Promise的状态会根据它的参数发生变化。当参数为空或非Promise时,当前状态变成fulfilled;当参数是Promise时,当前Promise的状态和参数的相同。

(2)在执行器中的异步操作错误时会调用reject()函数,当前Promise的状态会变成rejected。

  resolve()和reject()都能接收一个参数(即决议结果),前者的参数可以是本次操作的结果;而后者的参数可以是操作失败的理由,它们都会传递给下一个异步操作。

三、then()

  在生成Promise实例之后,就能通过then()方法绑定状态变化后的回调函数(即处理方法),如下代码所示,此处是异步操作同步化的关键。

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

  then()方法的两个参数,可分别指定状态变成fulfilled和rejected后的回调函数,而这两个回调函数的参数分别来自于resolve()和reject()函数。通过then()方法的这两个回调函数就能清晰的反馈出异步操作是否成功执行了。

  由于then()方法的返回值是一个新的Promise实例,因此可以链式调用then()方法,按顺序绑定回调函数,如下所示。

var chain = new Promise(function(resolve, reject) {
reject("error");
});
chain.then(null, function(reason) {
console.log(reason); //"error"
return "end";
})
.then(function(value) {
console.log(value); //"end"
});

  虽然第一个then()方法中的已完成的回调函数是null,但并不会终止数据的传递,仍然是先输出“error”,再输出“end”。之所以是这样的输出顺序,与回调函数的执行顺序有关。在then()方法链中,当前Promise的状态会决定下一个then()方法执行哪个回调函数,而这个状态又会受回调函数和它的返回值的影响,具体如下所列。

(1)当返回值是一个非Promise的值时,其状态会变成fulfilled。

(2)当回调函数抛出一个错误时,其状态会变成rejected。

(3)当返回值是一个Promise时,其状态与返回值的相同。

  下面有一个示例,描述了第三种情况,其中Promise.resolve()创建了已完成的Promise,相当于新创建一个在执行器中调用resolve()函数的Promise;Promise.reject()创建了已拒绝的Promise;catch()方法能处理拒绝的回调函数。这些都将在随后的章节中做详细介绍。

var chain = new Promise(function(resolve, reject) {
resolve();
});
chain.then(function(value) {
return Promise.resolve("fulfilled");
//相当于
return new Promise(function(resolve) {
resolve("fulfilled");
});
})
.then(function(value) {
console.log(value); //"fulfilled"
return Promise.reject("rejected");
})
.catch(function(reason) {
console.log(reason); //"rejected"
});

四、thenable

  包含then()方法的对象被称为thenable,所有的Promise都是thenable,下面是一个自定义的thenable,then()方法的参数含义与Promise中的相同。

let tha = {
then(resolve, reject) {
reject("thenable");
}
};

  Promise.resolve()能接收一个thenable,并返回一个新的Promise实例,将上一个示例的tha对象传递给它,如下所示。

Promise.resolve(tha)
.catch(function(reason) {
console.log(reason); //thenable
}).then(function() {
console.log("end");
});

  Promise.resolve()能将thenable转换成已完成或已拒绝的Promise,其最终的状态取决于thenable的then()方法,像这个示例中的then()方法调用了reject()函数,因此新的Promise的状态是已拒绝(rejected)。

五、错误处理

  Promise的catch()方法可以捕获并处理前一个异步操作中抛出的错误,它能接收一个已拒绝的回调函数(onRejected),其行为相当于调用一个忽略已完成的回调函数的then()方法,例如像下面这样第一个参数传null或undefined。

catch(onRejected)
//相当于
then(null, onRejected)
then(undefined, onRejected)

  下面是一个使用了catch()方法的例子,先在执行器中抛出一个错误,然后在catch()方法中处理。

var error = new Promise(function(resolve, reject) {
throw "error info";
});
error.catch(function(reason) {
console.log(reason); //"error info"
});

  如果Promise的状态在抛出错误之前被改变,那么这个错误就不能被catch()方法捕获,如下所示。

var error = new Promise(function(resolve, reject) {
resolve();
throw "error info";
});
error.catch(function(reason) {
console.log(reason); //不会输出
});

  在执行器中,由于resolve()函数在throw语句之前被调用,因此“error info”这句错误理由就不能在catch()方法中输出。

  在链式的Promise中,一旦发生错误,那么这个错误在没被捕获前,会一直传递下去。为了确保所有的错误都能被处理,可在链的末尾加上catch()方法。

ES6躬行记(22)——Promise的更多相关文章

  1. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  2. ES6躬行记 笔记

    ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向

  3. ES6躬行记(23)——Promise的静态方法和应用

    一.静态方法 Promise有四个静态方法,分别是resolve().reject().all()和race(),本节将着重分析这几个方法的功能和特点. 1)Promise.resolve() 此方法 ...

  4. ES6躬行记(13)——类型化数组

    类型化数组(Typed Array)是一种处理二进制数据的特殊数组,它可像C语言那样直接操纵字节,不过得先用ArrayBuffer对象创建数组缓冲区(Array Buffer),再映射到指定格式的视图 ...

  5. ES6躬行记(21)——类的继承

    ES6的继承依然是基于原型的继承,但语法更为简洁清晰.通过一个extends关键字,就能描述两个类之间的继承关系(如下代码所示),在此关键字之前的Man是子类(即派生类),而在其之后的People是父 ...

  6. ES6躬行记(3)——解构

    解构(destructuring)是一种赋值语法,可从数组中提取元素或从对象中提取属性,将其值赋给对应的变量或另一个对象的属性.解构地目的是简化提取数据的过程,增强代码的可读性.有两种解构语法,分别是 ...

  7. ES6躬行记(7)——代码模块化

    在ES6之前,由于ECMAScript不具备模块化管理的能力,因此往往需要借助第三方类库(例如遵守AMD规范的RequireJS或遵循CMD规范的SeaJS等)才能实现模块加载.而自从ES6引入了模块 ...

  8. ES6躬行记(4)——模板字面量

    模板字面量(Template Literal)是一种能够嵌入表达式的格式化字符串,有别于普通字符串,它使用反引号(`)包裹字符序列,而不是双引号或单引号.模板字面量包含特定形式的占位符(${expre ...

  9. ES6躬行记(15)——箭头函数和尾调用优化

    一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...

随机推荐

  1. haproxy 学习

    https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-option%20tcp-check https://www.hapro ...

  2. adf常用方法总结

    1.使用clientAttribute传值.获取值 或组件上面放客户端属性 <af:selectBooleanCheckbox text="" label="&qu ...

  3. 【转】Repository 返回 IQueryable?还是 IEnumerable?

    这是一个很有意思的问题,我们一步一步来探讨,首先需要明确两个概念(来自 MSDN): IQueryable:提供对未指定数据类型的特定数据源的查询进行计算的功能. IEnumerable:公开枚举数, ...

  4. UI设计是青春饭?今天告诉你真相!

    最近有学员来问,“我想转行学习UI设计,但是听很多人说,UI设计是吃青春饭的,互联网公司是不是只选择年轻的血液而淘汰年纪大的?”今天,我来统一回答一下. UI设计是不是青春饭? 我们先来思考一个问题: ...

  5. 教你用PS修复老照片

    原图素材虽然很旧,不过人物部分并没有怎么损坏,只是有一些色块和杂色.修复的工程相对来说也少很多.只需要给人物磨好皮,然后把暗调和高光部分调出来即可.原图     一.打开原图素材,按Ctrl + J ...

  6. 学习类App原型制作分享-Wokabulary

    Wokabulary是一款多功能词汇学习App,可以学习多国语言词汇.原型的引导页面采用的图片+文字+分页器,需要注意的是分页器选中位置要与页面顺序一致.其次是语言的选择页面,在前面给大家介绍过滚动区 ...

  7. /^\s+|\s+$/g 技术 内容

    alert(" aa dd ".replace(/^\s+|\s+$/g,'')+"方式的"); //正则表达式解释:分成两部分,^\s+ 以空格开头,\s+$ ...

  8. part1:8-远程登录Linux

    Linux远程登录 Linux系统中是通过ssh服务实现的远程登录功能.默认ssh服务开启了22端口,而且在安装完成系统时,这个服务已经安装,并且是开机启动的.所以不需要额外配置就能直接远程登录Lin ...

  9. [转载红鱼儿]Delphi实现微信开发(3)如何使用multipart/form-data格式上传文件

    开始前,先看下要实现的微信接口,上传多媒体文件,这个接口是用Form表单形式上传的文件.对我来说,对http的Form表单一知半解,还好,查到这个资料,如果你也和我一样,必须看看这篇文章. 在xali ...

  10. MyEclipse配置Maven插件

    一.工具环境 1.jdk-7u80-windows-x64 2.apache-tomcat-7.0.70 3.apache-maven-3.3.9 4.MyEclipse 10.7 5.windows ...