一、什么是Promise?
概念:
Promise,直译为“承诺”,是异步编程的一种解决方案,ECMAscript 6 原生提供了 Promise 对象。
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
用途:
一般来讲,Promise可以用来避免异步操作函数里的嵌套回调(callback hell)问题,但其实Promise本身就是一种比回调异步更强大的异步解决方案。
JS是单线程非阻塞语言,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
而解决异步最直接的方法是回调嵌套,但如果回调次数太多,就很容易进入“回调地狱”。
setTimeout(function () { console.log("第1秒,做第一件事"); setTimeout(function () { console.log("第2秒,做第二件事"); setTimeout(function () { console.log("第3秒,做第三件事"); setTimeout(function () { console.log("第4秒,做第四件事"); setTimeout(function () { console.log("第5秒,做第五件事"); setTimeout(function () { console.log("第6秒,做第六件事"); }, 1000); }, 1000); }, 1000); }, 1000); }, 1000); }, 1000);
如上,这样的层层嵌套,越多越恐怖,代码不清晰,易读性直线下降,维护难度直线上升。而Promise就可以解决这个问题。
注:Promise最早由社区提出并有多种开源实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。
Promise最大的好处其实是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了,见下文。
实现异步的方法目前有,自定义嵌套、Promise、generator、Defferd,还有ES7的async(其实是generator的封装),
不同场景可以选择不同的方式去实现。
二、Promise 对象
1、Promise 创建
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由JavaScript引擎提供,不用自己部署。
要想创建一个 promise 对象,直接使用 new 来调用 Promise 的构造器进行实例化。
var promise = new Promise(function (resolve, reject) { // 异步处理 if (true/* 异步操作成功 */) { resolve(value); } else { reject(error); } });
注:很多时候不写reject,因为只期望它成功,但如果不写,就没法报错(处理失败的情况)。
2、Promise 状态和PromiseValue
每个Promise对象都有三种状态:pending(已就绪)、fulfilled(已成功)和 rejected(已失败)。
当对象创建成功,即为pending状态,如果成功,通过调用resolve方法变为fulfilled状态,如果失败,通过调用reject方法变为rejected状态。
改变状态时,可以选择传递消息(value或error),就等于PromiseValue的值,也可以不传,PromiseValue会等于undefined,其实“状态改变”本身就是一个消息,。
一旦创建即执行,相当于立即执行,然后返回了一个pending状态的Promise对象
调用方法改变状态:
resolve: 把Promise状态 从 pending 变为 resolved ,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject: 把Promise状态 从 pending 变为 reject ,在异步操作失败时调用,并把错误作为参数传递出去
注:上述例子每次给pro变量赋值了一个新的Promise对象,因为只有Promise对象本身的处理结果能够改变它的状态,任何其他操作都无法改变Promise对象的状态。
状态的特点:
状态只能通过resolve和reject方法改变,并且一旦调用一次改变后,状态就定型(PromiseValue也会固定)不会再变。所以Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变两种情况。
3、then 方法
Promise实例生成后,可用then
方法分别指定两种状态的回调函数,即为Promise实例添加状态改变时的回调函数。并且可以接住resolve或reject传递的参数(结果)。
具体实现:
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,必选,第二个参数是 Promise 执行失败时的回调,可选;两个函数只会有一个被调用。
promise.then(function (value) { //成功时调用 value即为resolve传递的结果 不传为undefined }, function (error) { //失败时调用 error即为reject传递的结果 不传为undefined });
then 方法的特点:在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
(function runme() { var i = 0; new Promise(function (resolve) { resolve(); }).then(function () { i++ }); alert(i);//0 此时回调函数还没有执行 })()
then方法会返回一个Promise对象,这个对象的PromiseValue通过then调用的函数里的return赋值。
注:如果return的是一个Promise对象,then就会直接返回这个对象。
4、链式写法
then方法返回的是一个新的Promise实例,(注意:不是原来那个Promise实例)。因此可以采用链式写法。
A().then(function (people) { return Promise.all([B(people), C(people)]); }).then(function (people) { D(people); }).catch(function(error){ throw error; });
注:A().then(function (people) {} 返回的对象就是 Promise.all([B(people), C(people)])得到的对象。
Promise.all(),见下文。
5、catch方法
catch 相当于 .then(null, rejection)的别名,用于指定发生错误时的回调函数。then函数中的第二个参数常常被省略了,然后被这个catch方法替代。
一旦catch前面的任何一个Promise发生异常(调用reject方法),都会被catch捕获,包括Promise函数创建的Promise,还有.then返回的Promise,甚至catch前面如果还有一个catch在这个catch抛出的异常(使用throw语句)也会被后一个catch捕获。
注:此次catch方法返回的是一个resolved状态的Promise对象。
注:此次catch方法返回的是一个rejected状态的Promise对象。
继续.catch
注:可以看到前面catch抛出的异常成功的被下一个catch捕获。
也就是说:
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,即,错误总会被下一个catch语句捕获。
所以通常会这么写:
promise.then().catch() promise.then().then().catch() promise.then().then().catch().then().catch()
let p1 = new Promise((resolve) => { setTimeout(function () { console.log(1); resolve(1); }, 1000) }); let p2 = new Promise((resolve) => { setTimeout(function () { console.log(2); resolve(2); }, 3000) }); let p3 = new Promise((resolve) => { setTimeout(function () { console.log(3); resolve(3); }, 4000) }); let pp = Promise.all([p1, p2, p3]);
7、现有对象转为Promise对象
Promise.resolve()方法就起到这个作用
Promise.resolve('foo'); // 等价于 new Promise(function (resolve) { resolve('foo') });
Promise.resolve()方法的参数分成四种情况:
参数是一个Promise实例 :不做任何修改、原封不动地返回这个实例。
参数是一个thenable对象 :将这个对象转换为Promise对象,然后立刻执行thenable的then方法
参数是普通值(除了Promise对象和thenable对象的所有值),基础复杂类型函数都可以:反回一个新的Promise对象,状态为resolved,PromiseValue等于传入的参数。
不带有任何参数::直接返回一个resolved状态的Promise对象
注:立即resolved的对象,是在本轮事件循环结束时执行,而不是在下一轮循环时间开始时执行
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one');
上面代码中,setTimeout(fn,0),在下一轮循环事件开始执行,Promise.resolve()在本轮事件循环结束时执行,console.log('one')立刻执行,
因此上面的打印顺序是 one two three。
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
三、扩展和应用
1、try catch
try catch是JavaScript的异常处理机制,把可能出错的代码放在try语句块中,如果出错了,就会被catch捕获来处理异常。如果不catch,一旦出错就会造成程序崩溃。
如果有多个await命令,可以将其都放在try catch结构中,如果执行出错,catch就会捕获异常。
2、ajax:把执行代码和处理结果的代码清晰地分离
function ajax(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var URL = "/try/ajax/testpromise.php"; ajax(URL).then(function (value){//如果AJAX成功 console.log('内容是:' + value); }).catch(function (error){//如果AJAX失败 console.log('错误:' + error); });
3、状态传递,等待任务
var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); })
上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。
4、解决回调地狱
function async1() { let pro = new Promise(function (resolve, reject) { //做一些异步操作 setTimeout(function () { console.log('异步任务1执行完成'); resolve('传递结果1'); }, 1000); }); return pro; } function async2() { let pro = new Promise(function (resolve, reject) { //做一些异步操作 setTimeout(function () { console.log('异步任务2执行完成'); resolve('传递结果2'); }, 2000); }); return pro; } function async3() { let pro = new Promise(function (resolve, reject) { //做一些异步操作 setTimeout(function () { console.log('异步任务3执行完成'); resolve('传递结果3'); }, 2000); }); return pro; } async1() .then(function (data) { console.log(data); return async2(); }) .then(function (data) { console.log(data); return async3(); }) .then(function (data) { console.log(data); }); // 输出结果: // 异步任务1执行完成 // 传递结果1 // 异步任务2执行完成 // 传递结果2 // 异步任务3执行完成 // 传递结果3