介绍
Promise是异步编程的一种解决方案
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,它是一个对象,从它可以获取异步操作的消息。
对象的状态不受外界影响,Promise对象代表这一个异步操作,有三个状态:如下,只有异步操作的结果才可以决定当前是哪一种状态
- Pending(进行中)
- Resolved(已完成,又称Fulled)
- Rejected(已失败)
一旦状态改变,就不会再变,任何时候都可以得到这个结果,Promised对象的状态改变,只有两种,如下:,只要这两种情况发生,状态就凝固了,不会再变了。会一直保持这个结果。
- 从Pending变为Resolved
- 从Pending变为Rejected
基本用法
- Promise对象是一个构造函数,用来生成Promise对象
1 | var promise = new Promise(function(resolve, reject) { |
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(从Pending变为Resolved),在异步操作成功的时候调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(从Pending变为Rejected),在异步操作失败的时候调用,并将异步操作返回的错误,作为参数传递出去。
- Promise实例生成之后,可以用then方法指定Resolved和Reject状态的回调函数,两个参数,一个成功回调,一个失败回调,第二个参数失败回调可选
1 | promise.then(function (value) { |
例如下例:
1 | function timeout(ms) { |
上例中:timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。
Promise新建后就会立即执行
例如下例:
1 | let promise = new Promise(function(resolve, reject) { |
上例中:Promise实例新建后立即执行,先输出“Promise”,然后then方法指定回调,当时回调会在当前脚本的同步方法之后才会执行,所以其次是输出“Hi!”,最后才会输出回调里面的“Resolved”
下面是异步加载图片的例子:
1 | function loadImageAsync(url) { |
下面是用Promise对象实现Ajax操作的例子
1 | var getJSON = function(url) { |
resolve函数参数除了可能是正常的值以外也可能是另一个Promise实例,即回调里面是请求,如下例:
1 | var p1 = new Promise(function (resolve, reject) { |
上例中:p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即p1的状态决定了p2的状态。如果p1是Pending,那么p2就必须要等待。如果p1是Resolved或Rejected,那么p2的回调函数将会立即执行。
1 | var p1 = new Promise(function (resolve, reject) { |
上面代码中:p1是一个Promise,3s后变为rejected。p2的状态在1s之后改变,resolve方法返回的是p1。此时,由于p2返回的是另一个Promise,所以后面的then语句都变成针对后者(p1)。又过了2s,p1变为rejected,导致出发catch方法指定的回调函数。
Promise.prototype.then()
then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调。前面说过,then方法的第一个参数是Resolved成功后的回调,第二个参数(可选)是Rejected失败后的回调。
then方法返回的是一个新的Promise实例,因此可以采用链式写法,及then方法后再调用一个then方法
1 | getJSON("/posts.json").then(function(json) { |
上面的代码中,使用then方法一次指定了两个回调,第一个回调完成以后,会将返回的结果作为参数,传入第二个回调。
这种链式的then可以指定一组按照次序调用的回调。这时,前一个回调有可能还是一个Promise实例,这时后一个就必须等待。如下例:
1 | getJSON("/post/1.json").then(function(post) { |
上例中:第一个then方法指定的回调是一个Promise实例。这时,第二个then方法指定的回调,就会等待这个新的Promise对象状态先发生变化。如果变为Resolved,就调用funcA,如果变为Rejected,就调用funcB。
采用箭头函数:
1 | getJSON("/post/1.json").then( |
Promise.prototype.catch()
Promise.ptototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
1 | getJSON('/posts.json').then(function(posts) { |
上例中:如果该对象状态变为Resolved,会调用then方法指定的回调,如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调。另外,如果运行中抛出错误,也会被catch捕获。
1 | p.then((val) => console.log('fulfilled:', val)) |
如下例:
1 | var promise = new Promise(function(resolve, reject) { |
promise抛出一个错误,就会被catch方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
1 | // 写法一 |
上述也说明,reject方法的作用,等同于抛出错误。
如果Promise状态已经变成Resolved,再抛出错误是无效的。
1 | var promise = new Promise(function(resolve, reject) { |
上面代码中:Promise在resolve语句后面,再抛出错误,不会被捕获,等同于没有抛出。因为Promise的状态一旦改变,就永久保持该状态,不会再改变了。
一般来说,不要在then方法里面定义Rejected失败状态的回调,应该使用catch语句
1 | // bad |
因为,Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获。错误总可以被catch语句捕获。
then catch语句更容易捕获错误,而且接近同步的写法(try/catch)语句。
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
1 | var p = Promise.all([p1, p2, p3]); |
Promise.all的参数可以不是数组,但必须具有接口,且返回的每个成员都是Promise实例
p的状态有p1、p2、p3决定,分成两种情况
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的回调
只要p1、p2、p3的状态有一个被rejected,p的状态就会变成rejected,此时,第一个被rejected的实例的返回值,会传递给p的回调
Promise.race()
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
1 | var p = Promise.race([p1, p2, p3]); |
Promise.resolve()
有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用
1 | var jsPromise = Promise.resolve($.ajax('/whatever.json')); |
Promise.resolve等价于下面的写法
1 | Promise.resolve('foo') |
Promise.reject()
Promise.reject方法也会返回一个新的Promise实例,状态未rejected
1 | var p = Promise.reject('出错了'); |
应用
将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
1 | const preloadImage = function (path) { |