var p = new Promise(fn);
p.then(function(data) {
console.log('resolve: ', data);
}, function(data) {
console.log('reject: ', data);
})
function fn(resolve, reject) {
console.log('begin to execute!');
var number = Math.random();
if(number<=0.5) {
resolve('less than 0.5');
} else {
reject('greater than 0.5');
}
}
一、Promise 概述
1.1、Promise 的诞生
在没有
Promise
之前,我们的异步程序流程编码中,可能会遇到类似这样的情况:这种回调函数嵌套 (Callback Nested)的现象经常被叫做
回调地狱
(Callback Hell),也被叫做回调金字塔
(Pyramid of Doom),专指由于代码不断嵌套/缩进
所形成的金字塔形状,嵌套/缩进越多金字塔形状越明显。这种体验很差,在这种情况下诞生了
Promise
,它最早由社区提出和实现,ES6
将其写进了语言标准,统一了用法,提供了原生Promise
对象。👍 目前,市面上各种浏览器对
ES6
的支持已经达到 90% 以上。1.2、什么是 Promise
Promise
是ES6
提供的一种新的异步编程解决方案,完全改变了JavaScript
异步编程的写法,让异步编程变得简洁优雅自然可读,比传统的解决方案——回调函数和事件更合理、更强大。最重要的是 可读性更友好所谓
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果, 即“过一会儿给你结果”。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的API
,各种异步操作都可以用同样的方法进行处理。Promise
字面上理解即承诺的意思,new
一个Promise
就是新建一个承诺。在新建一个承诺的时候你需要指定承诺是否实现的标准,即Promise
构造器中设置一个函数作为参数,这个函数内部指定了承诺是否实现的标准。1.3、Promise 的使用方法
ES6
规定,Promise
对象是一个构造函数,用来生成Promise
实例。下面代码创造了一个Promise
实例。我们来看下步骤: 🔢
1、
new
操作符调用Promise
构造函数,并接受一个函数作为Promise
构造函数的参数,该函数有两个参数,resolve
和reject
, 而resolve
和reject
是两个函数,由原生的Promise
的API
提供,不用自己部署。 2、resolve
用来将Promise
对象的状态置为成功,并将异步操作结果value
作为参数传递给成功回调函数 3、reject
用来将Promise
对象的状态置为失败,并将异步操作结果error
作为参数传递给失败回调函数 4、Promise
实例生成以后,可以调用then
方法,并设置两个函数,即分别指定resolved
状态和rejected
状态的回调函数。通俗的讲,then
方法绑定两个回调函数,第一个用来处理Promise
成功状态,第二个用来处理Promise
失败状态,其中,第二个参数是可选的。注意: 调用
resolve
或reject
并不会终结Promise
的参数函数的执行。 👍上面代码中,调用
resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为立即resolved
的Promise
是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。一般来说,调用
resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。 👍二、Promise 状态及状态变化
下图是 Promise 对象的状态变化图
从上图我们可以看出,
Promise
对象只有 三种状态: 🔢Pending
(进行中)FullFilled
(已完成,又称为Resolved
)Rejected
(已失败)Promise
对象的 状态改变 只有两种: 🔢Pending
-->Resolved
Pending
-->Rejected
Promise
对象有两个特点: 1、对象状态只由异步操作结果决定。resolve
方法会使Promise
对象由pendding
状态变为fulfilled
状态;reject
方法或者异常会使得Promise
对象由pendding
状态变为rejected
状态。Promise
状态变化只有上图这两条路径。 2、对象状态一旦改变,任何时候都能得到这个结果。即状态一旦进入fulfilled
或者rejected
,promise
便不再出现状态变化,同时我们再添加回调会立即得到结果。这点跟事件不一样,事件是发生后再绑定监听,就监听不到了。Promise 的缺点 有了
Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。但是
Promise
也有一些缺点: 🔢Promise
,一旦新建它就会立即执行,无法中途取消。Promise
内部抛出的错误,不会反应到外部。pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。三、使用 Promise 的流程
使用 Promise 的流程。
new Promise(fn)
返回一个promise
对象fn
中指定异步等处理resolve
(处理结果值)reject
(Error对象)这个例子当中,在
fn
当中产生一个0~1
的随机数,如果小于等于0.5
, 则调用resolve
函数,大于0.5
,则调用reject
函数。函数定义好之后,用Promise
包裹这个函数,返回一个Promise
对象,然后调用对象的then
方法,分别定义resolve
和reject
函数。这里resolve
和reject
比较简单,就是把传来的参数加一个前缀然后打印输出。下面是一个 异步加载图片 的例子
上面代码中,使用
Promise
包装了一个图片加载的异步操作。如果加载成功,就调用resolve
方法,否则就调用reject
方法。下面是一个用
Promise
对象实现的Ajax
操作的例子:上面代码中,
getRequest
是对XMLHttpRequest
对象的封装,用于发出一个HTTP
请求,并且返回一个Promise
对象。需要注意的是,在getRequest
内部,resolve
函数和reject
函数调用时,都带有参数。四、Promise.prototype.then() 方法
每一个
Promise
实例对象都有一个then
方法,实际上then
方法是定义在原型对象Promise.prototype
上的函数,它的作用是为Promise
实例对象设置状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。ps 关于原型和原型对象请阅读《JavaScript 原型及原型对象》
then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。上面的代码使用
then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。五、Promise.prototype.catch() 方法
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。上面代码中,
getRequest
方法返回一个Promise
对象,如果该对象状态变为resolved
,则会调用then
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。上面代码中,
promise
抛出一个错误,就被catch
方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。 👍比较上面两种写法,可以发现
reject
方法的作用,等同于抛出错误。如果 Promise 状态已经变成resolved,再抛出错误是无效的。 👍
一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。 👍
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面
then
方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch
方法,而不使用then
方法的第二个参数。Reference