所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。es6
特色:编程
(1)对象的状态不受外界影响。数组
Promise对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。promise
(2)一旦状态改变,就不会再变,任什么时候候均可以获得这个结果。服务器
缺点:数据结构
(1)没法取消Promise,一旦新建它就会当即执行,没法中途取消。app
(2)若是不设置回调函数,Promise内部抛出的错误,不会反应到外部。异步
(3)当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。async
Promise 新建后就会当即执行。异步编程
then方法指定的回调函数,将在当前脚本全部同步任务执行完才会执行。
对于Promise的嵌套,例如p1中resolve或者reject p2,那么p1的状态取决于p2.
Promise.prototype.then
做用是为 Promise 实例添加状态改变时的回调函数。
第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。
所以能够采用链式写法,即then方法后面再调用另外一个then方法。
采用链式的then,能够指定一组按照次序调用的回调函数。
执行时间:会在本轮“事件循环”(event loop)的结束时执行。
Promise.prototype.catch
catch是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
若是 Promise 状态已经变成resolved,再抛出错误是无效的。
Promise 对象的错误具备“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误老是会被下一个catch语句捕获。
Promise.prototype.finally
用于指定无论 Promise 对象最后状态如何,都会执行的操做。
无论promise最后的状态,在执行完then或catch指定的回调函数之后,都会执行finally方法指定的回调函数。
例子,服务器使用 Promise 处理请求,而后使用finally方法关掉服务器。
Promise.all
用于将多个 Promise 实例,包装成一个新的 Promise 实例。
若是不是 Promise 实例,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
Promise.all方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。
两种状况:
(1)只有p一、p二、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p一、p二、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
注意,若是做为参数的 Promise 实例,本身定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
Promise.race
一样是将多个 Promise 实例,包装成一个新的 Promise 实例。
若是不是 Promise 实例,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
只要多个实例之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
例子,若是指定时间内没有得到结果,就将 Promise 的状态变为reject,不然变为resolve。(请求超时)
Promise.resolve
将现有对象转为 Promise 对象,Promise.resolve方法就起到这个做用。
四种状况:
(1)参数是一个 Promise 实例
将不作任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
thenable对象指的是具备then方法的对象,会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。
(3)参数不是具备then方法的对象,或根本就不是对象
若是参数是一个原始值,或者是一个不具备then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
Promise.resolve方法的参数,会同时传给回调函数。
(4)不带有任何参数
容许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
因此,若是但愿获得一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。
须要注意的是,当即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
Promise.reject
也会返回一个新的 Promise 实例,该实例的状态为rejected。
注意,Promise.reject()方法的参数,会原封不动地做为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
Promise.try
实际开发中,常常遇到一种状况:
不知道或者不想区分,函数f是同步函数仍是异步操做,可是想用 Promise 来处理它。
由于这样就能够无论f是否包含异步操做,都用then方法指定下一步流程,用catch方法处理f抛出的错误。
通常就会采用下面的写法:
Promise.resolve().then(f)
上面的写法有一个缺点,就是若是f是同步函数,那么它会在本轮事件循环的末尾执行。
鉴于这是一个很常见的需求,因此如今有一个提案,提供Promise.try方法替代上面的写法。
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数彻底不一样。
多种理解角度:
语法上,首先能够把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数,可是有两个特征。
(1)function关键字与函数名之间有一个星号;
(2)函数体内部使用yield表达式,定义不一样的内部状态(yield在英语里的意思就是“产出”)。
Generator 函数的调用方法与普通函数同样,也是在函数名后面加上一对圆括号。
不一样的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,所以等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
注意:
yield表达式只能用在 Generator 函数里面,用在其余地方都会报错。(定义的时候就会报错,不会等到next时)
yield表达式若是用在另外一个表达式之中,必须放在圆括号里面。
yield表达式用做函数参数或放在赋值表达式的右边,能够不加括号。
yield与return:
类似之处在于,都能返回紧跟在语句后面的那个表达式的值。
区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具有位置记忆的功能。
Generator 函数能够不用yield表达式,这时就变成了一个单纯的暂缓执行函数。
与 Iterator 接口的关系
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
因为 Generator 函数就是遍历器生成函数,所以能够把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具备 Iterator 接口。
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
Generator 函数执行后,返回一个遍历器对象。该对象自己也具备Symbol.iterator属性,执行后返回自身。
function* gen(){ … } var g = gen(); g[Symbol.iterator]() === g
next 方法的参数
yield表达式自己没有返回值,或者说老是返回undefined。
next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。(第一个next方法的参数是无效的)
for...of 循环
for...of循环能够自动遍历 Generator 函数时生成的Iterator对象,且此时再也不须要调用next方法。
注意,一旦next方法的返回对象的done属性为true,for...of循环就会停止,且不包含该返回对象。(return语句,不包括在for...of循环之中)
利用for...of循环,能够写出遍历任意对象(object)的方法。
原生的 JavaScript 对象没有遍历接口,没法使用for...of循环,经过 Generator 函数为它加上这个接口,就能够用了。
for...of循环、扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。
Generator.prototype.throw
Generator 函数返回的遍历器对象,都有一个throw方法,能够在函数体外抛出错误,而后在 Generator 函数体内捕获(只能捕获一次)。
注意:遍历器对象的throw与全局对象的throw方法是不同的。
若是Generator函数中没有定义catch语句,就会被外部的catch捕获。
Generator.prototype.return
返回给定的值,而且终结遍历 Generator 函数。
遍历器对象调用return方法后,返回值的value属性就是return方法的参数,返回值的done属性为true。
而且,Generator 函数的遍历就终止了,之后再调用next方法,done属性老是返回true。
注意:若是 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。(即最后才执行return方法)
next()、throw()、return()的共同点
next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个throw语句
return()是将yield表达式替换成一个return语句。
yield* 表达式
若是在 Generator 函数内部,调用另外一个 Generator 函数,默认状况下是没有效果的。
这个就须要用到yield*表达式,用来在一个 Generator 函数里面执行另外一个 Generator 函数。
从语法角度看,若是yield表达式后面跟的是一个遍历器对象,须要在yield表达式后面加上星号,代表它返回的是一个遍历器对象。这被称为yield*表达式。
yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。
反之,在有return语句时,则须要用var value = yield* iterator的形式获取return语句的值。
若是yield*后面跟着一个数组,因为数组原生支持遍历器,所以就会遍历数组成员。
实际上,任何数据结构只要有 Iterator 接口,就能够被yield*遍历。
yield*命令能够很方便地取出嵌套数组的全部成员。
做为对象属性的 Generator 函数
let obj = { * myGeneratorMethod() { ··· } };
Generator 函数的this
Generator 函数在this对象上面添加了一个属性a,可是生成的遍历器对象拿不到这个属性。
new命令跟Generator 函数一块儿使用,结果报错,由于Generator 函数不是构造函数。
那么,有没有办法让 Generator 函数返回一个正常的对象实例,既能够用next方法,又能够得到正常的this?
使用call或者apply方法,动态的绑定Generator 函数内的this对象,使用Generator 函数的原型对象便可。
应用
(1)异步操做的同步化表达
使用同步的方式书写函数,在异步的回调中调用next方法
(2)控制流管理
若是有一个多步操做很是耗时,采用回调函数,会出现多层嵌套。
使用Promise会进一步改善代码流程,使用Generator 函数还能够进一步改善代码运行流程。而后,使用一个函数,按次序自动执行全部步骤。
(3)部署 Iterator 接口
利用 Generator 函数,能够在任意对象上部署 Iterator 接口。
(4)做为数据结构
Generator 能够看做是数据结构,更确切地说,能够看做是一个数组结构,
由于 Generator 函数能够返回一系列的值,这意味着它能够对任意表达式,提供相似数组的接口。
Generator 函数的异步应用
传统方法
回调函数
事件监听
发布/订阅
Promise 对象
Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
基本概念
Generator 函数的数据交换和错误处理
Generator 函数能够暂停执行和恢复执行,这是它能封装异步任务的根本缘由。
除此以外,它还有两个特性,使它能够做为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
Thunk 函数
Thunk 函数是自动执行 Generator 函数的一种方法。
含义
编译器的“传名调用”实现,每每是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫作 Thunk 函数。
JavaScript 语言的 Thunk 函数
JavaScript 语言是传值调用,它的 Thunk 函数含义有所不一样。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数做为参数的单参数函数。
最关心的问题怎么将异步变成同步?
(同步就使用 await ,而 await 后面接一个 Promise 便可,会同步的等待而且拿到 Promise的结果)
含义
ES2017 标准引入了 async 函数,使得异步操做变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体如今如下四点:
(1)内置执行器
Generator 函数的执行必须靠执行器,因此才有了co模块,而async函数自带执行器。
也就是说,async函数的执行,与普通函数如出一辙,只要一行。
(2)更好的语义
async表示函数里有异步操做,await表示紧跟在后面的表达式须要等待结果。
(3)更广的适用性
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,
而async函数的await命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操做)。
(4)返回值是 Promise has of
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。
基本用法
async函数返回一个 Promise 对象,可使用then方法添加回调函数。
语法
async函数的语法规则整体上比较简单,难点是错误处理机制。
返回 Promise 对象
async函数返回一个 Promise 对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数内部抛出错误,会致使返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部全部await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。
也就是说,只有async函数内部的异步操做执行完,才会执行then方法指定的回调函数。
await 命令
正常状况下,await命令后面是一个 Promise 对象。若是不是,会被转成一个当即resolve的 Promise 对象。
await命令后面的 Promise 对象若是变为reject状态,则reject的参数会被catch方法的回调函数接收到。
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。(若是不但愿中断执行能够try...catch)
另外一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
错误处理
若是await后面的异步操做出错,那么等同于async函数返回的 Promise 对象被reject。
防止出错的方法,也是将其放在try...catch代码块之中。
或者为await后面的Promise对象添加catch方法。
使用注意点
1、await命令后面的Promise对象,运行结果多是rejected,因此最好把await命令放在try...catch代码块中。
二、多个await命令后面的异步操做,若是不存在继发关系(比较耗时),最好让它们同时触发。
三、await命令只能用在async函数之中,若是用在普通函数,就会报错。
async 函数的实现原理
是将 Generator 函数和自动执行器,包装在一个函数里。
Promise 的写法比回调函数的写法大大改进,可是一眼看上去,代码彻底都是 Promise 的 API(then、catch等等),操做自己的语义反而不容易看出来。
Generator 函数语义比 Promise 写法更清晰,这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数。
Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。
它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,所以代码量最少。
若是使用 Generator 写法,自动执行器须要用户本身提供。
特别说明:本文中的不少内容都是参考阮一峰老师的ECMAScript 6 入门,若有转载请注明出处。