ES6 - Promise对象①:概述、then()、catch()
1.含义
Promise是异步编程的一种解决方案。所谓Promise,简单来说就是一个容器,里面保存这某个未来才会结束的事件(异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用相同的方法进行处理。
(1)Promise对象的两个特点
① 对象的状态不受外界影响
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
②一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise对象的状态改变,只有两种可能:从pending -> fulfilled和从pending -> rejected。只要这两种情况发生,状态就凝固了,不会再发生改变。
后面的resolved
统一只指fulfilled
状态,不包含rejected
状态。
(2)缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段
2.基本用法
(1)创造一个Promise实例
ES6规定,Promise对象是一个构造函数,用来生成Promise实例
1 const promise = new Promise(function(resolve,reject){ 2 3 // 其他代码 4 5 if(/* 异步操作成功 */){ 6 resolve(value) 7 }else{ 8 reject(err) 9 } 10 })
Promise构造函数接受一个函数作为参数,该函数有两个参数:resolve 和 rejected,它们是两个函数,由JavaScript引擎提供,不用自己部署:
- resolve函数:作用是将Promise对象的状态从“未完成 -> 成功”,即pending -> resolve。在异步操作成功时调用,并将异步操作的结果作为参数传递出去
- rejected函数:将Promise对象的状态从“未完成 -> 失败”,即pending -> rejected。在异步操作失败时调用,并将异步操作报的错误,作为参数传递出去
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
promise.then(function(value){ // 成功的回调 },function(err){ // 失败的回调 });
它们都接受Promise
对象传出的值作为参数.
示例:
1 function timeout(ms) { 2 return new Promise((resolve, reject) => { 3 setTimeout(() => { 4 resolve("异步执行结束") 5 }, ms) 6 }) 7 } 8 9 timeout(1000) 10 .then((value) => { console.log(value) }) // 1s 后输出:异步执行结束
上面代码中,timeout方法返回一个Promise实例,说明过一段时间才会发生结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
注意:Promise新建以后就会立即执行。
1 let promise = new Promise(function (resolve, reject) { 2 console.log("Promise"); 3 resolve() 4 }) 5 6 promise.then(function () { 7 console.log("异步结束"); 8 }) 9 10 console.log("hello world"); 11 12 // Promise 13 // hello world 14 // resolved
上面代码中,Promise新建以后立即执行,所以首先输出Promise。然后.then方法的回调函数,将在当前脚本所在同步任务执行完才会执行,所以异步结束最后输出。这里涉及到JS的事件循环,详细可见
注意:
- 即使没有调用.then()方法,promise内的log方法也会执行:
1 let promise = new Promise(function (resolve, reject) { 2 console.log("Promise"); 3 resolve() 4 }) // Promise
- 调用resolve或rejected并不会终结Promise的参数函数的执行。
1 let promise = new Promise(function (resolve, reject) { 2 console.log("Promise"); 3 resolve() 4 console.log("Promise仍在执行"); 5 }) 6 // Promise 7 // Promise仍在执行
- 一般来说,resolve以后,Promise的使命便结束了,后续操作应该放到.then方法里面,而不应该直接写在resolve或reject后面,所以最好加上return语句:
-
new Promise((resolve, reject) => { return resolve(1); // 后面的语句不会执行 console.log(2); })
-
3.Promise.prototype.then()
then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。
then方法的两个参数分别是resolved状态的回调函数和rejected状态的回调函数,它们都是可选的。
then方法返回的是一个新的Promise实例(不是原来的哪个Promise实例),因此可以使用链式写法(then后面接另一个then)
getJSON("/json").then(function(json){ return "json" }).then(function(){ // ... })
示例:
1 let promise = new Promise(function (resolve, reject) { 2 console.log("Promise"); 3 return resolve() 4 }) 5 6 promise.then(function () { 7 console.log("Promise结束"); 8 return "异步结束" 9 }).then((res)=>{ 10 console.log(res); 11 }) 12 13 // Promise 14 // Promise结束 15 // 异步结束
说明:第一个回调函数完成以后,会将返回结果(“异步结束”)作为参数传入第二个回调函数
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。
4.Promise.prototype.catch()
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); });
上面说过Promise的状态从pending -> rejected状态时会将异步操作的错误作为参数传递出去:
示例1:
1 let promise = new Promise(function (resolve, reject) { 2 console.log("Promise"); 3 return reject("发生不明错误") 4 }) 5 6 promise.then(function () { 7 }).catch(err => { 8 console.log(err); 9 }) 10 11 // Promise 12 // 发生不明错误
示例2:
1 const promise = new Promise(function (resolve, reject) { 2 throw new Error('test'); 3 }); 4 promise.catch(function (error) { 5 console.log(error); 6 }); 7 // Error:test
其实reject()方法的作用,等同于抛出错误。
注意:如果Promise状态已经变成了resolved,再抛出错误是无效的
Promise对象的错误具有“冒泡新值”,会一直向后传递,直到被捕获位置。也就是说,错误总是会被下一个catch语句捕获。
1 let promise = new Promise(function(resolve,reject){ 2 reject(123) 3 }) 4 5 promise.then() 6 .then() 7 .then() 8 .catch(err =>{ 9 console.log(err); 10 }) 11 // 123
建议:使用catch()方法,而不是使用then()方法的第二个参数来不湖泊then方法执行中的错误。
因外第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。
1 const someAsyncThing = function() { 2 return new Promise(function(resolve, reject) { 3 // 下面一行会报错,因为x没有声明 4 resolve(x + 2); 5 }); 6 }; 7 8 someAsyncThing().then(function() { 9 console.log('everything is great'); 10 }); 11 12 setTimeout(() => { console.log(123) }, 2000);
上面代码中,someAsyncThing()函数产生的Promise对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined。但是不会退出进程,终止脚本运行,2s后还是会输出123。说明Promise内部的错误不会影响到Promise外部的代码,通俗说法就是"Promise 会吃掉错误"。
一般总是建议,Promise 对象后面要跟catch()
方法,这样可以处理 Promise 内部发生的错误。catch()
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()
方法。如上面的示例:
1 const someAsyncThing = function () { 2 return new Promise(function (resolve, reject) { 3 // 下面一行会报错,因为x没有声明 4 resolve(x + 2); 5 }); 6 }; 7 8 someAsyncThing() 9 .catch(function (error) { 10 console.log('oh no', error); 11 }) 12 .then(function () { 13 console.log('carry on'); 14 }); 15 // oh no [ReferenceError: x is not defined] 16 // carry on
上面代码运行完catch方法指定的回调函数,会接着运行后面那个then()方法指定的回调函数。如果没有报错,则会跳过catch()方法
Promise.resolve() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); // carry on
上面代码中,carch()方法抛出了一个错误,因为后面没有别的catch()方法了,所以这个then中的错误不会被捕获,也不会传递到外层。如果改写以下,结果就不一样了。
1 const someAsyncThing = function () { 2 return new Promise(function (resolve, reject) { 3 // 下面一行会报错,因为x没有声明 4 resolve(x + 2); 5 }); 6 }; 7 8 someAsyncThing().then(()=>{ 9 return "hello" 10 }).catch((err)=>{ 11 console.log("张三"); 12 y + 2 13 }).catch(err=>{ 14 console.log("捕获错误:",err); 15 }) 16 // 张三 17 // 捕获错误: ReferenceError: y is not defined
这里用的是第二个catch()用来捕获第一个catch()方法抛出的错误