Promise 对象
-
Promise 的含义
Promise 是异步编程的一种解决方案,比传统的解决方案 《回调函数和事件》 更合理更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise, 简单是就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作) 的结果。 从语法上来讲,Promise是一个对象,从它可以获取异步操作的信息。Promise 提供了统一的API,各种异步操作都可以用同样的方法进行处理。
promise的两个特点
(1): 对象的状态不受外界的影响,Promise对象代表一个异步操作,有三种状态: pending(进行中)、fulfilled(已成功) 和 rejected(已失败)。 只有异步操作的结果,可以决定当前是哪一种状态。任何其他操作都无法改变这个状态。 这也是Promise 这个名字的由来。
(2) 一旦状态确定,就不会再变,任何时候都可以得到这个结果。Promise对象状态改变,只有两种可能,从pending 变为 fulfilled 和 从pending 变为 rejected 。 只要这两种情况发生,状态就凝固了, 不会再次改变,会一直保持这个结果,这时就称为resolved(已定型)。 如果改变已经发生了, 你再对Promise对象添加回调函数,也会立即监听到这个结果。这与事件(Event) 完全不同 , 事件的特点是, 如果你错过了它,再去监听, 是得不到结果的
(3) 有了Promise对象,就可以将异步操作以同步操作的流程表达出来, 避免了层层嵌套的回调函数,此外,Promise 对象提供了统一的接口,使得控制异步操作更加容器。
(4)Promise 也有一些缺点, 首先,无法取消Promise , 一旦新建它就会立即执行, 无法中途取消, 其次,如果不设置回调函数 ,Promise 内部抛出错误,会直接抛出到控制台,没有方法可以兜底。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(无法确定是刚刚开始 还是即将完成)。
function test(resolve , reject){ var timeOut = Math.random() * 2; console.log("设置时间为" + timeOut + "秒"); setTimeout(function(){ if(timeOut < 1){ console.log("执行成功"); resolve('200 OK'); }else{ console.log("执行失败"); reject("500 error"); } },timeOut * 1000) } var p1 = new Promise(test); var p2 = p1.then(function(result){ console.log('成功信息 ' + result); }) var p3 = p2.catch(function(a){ console.log('错误信息' , a); }) new Promise(test).then(() => console.log("执行成功")).catch(() => console.log("执行失败"))
-
基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
Promise 构造函数接受 一个函数作为参数,该函数的两个参数分别是resolve 和 reject 。 他们是两个函数, 由JavaScript引擎提供, 不用自己去实现写法!
resolve 函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved), 在异步操作成功时调用, 并将异步操作的结果,作为参数传递出去;reject 函数的作用是,将promise对象状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指出resolved状态和rejected状态的回调函数。
接下来看一个例子!
```javascript function timeout(ms){ return new Promise((resolve , reject) => { // setTimeout(// 要执行的函数,毫秒数 ,执行函数所需要的参数) setTimeout(resolve , ms , 'done'); }); } timeout(2000).then((value) =>{ console.log(value); })上面的代码中, timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果,过了指定的时间(ms)以后,Promise实例状态变为resolved , 就会触发then方法绑定回调函数。 <h3>通常情况下,Promise 再新建后会被 <span style='color:red'>立即执行</span> </h3> ```javascript let promise = new Promise(function(resolve , reject){ console.log("Promise 正在执行..."); // 返回了成功的内容 resolve(); }) promise.then(function(){ console.log("拿到返回值...."); }); console.log('你好啊!');
上面的代码中 ,Promise 新建后立即执行,所以首先输出的是Promise 。然后then方法指定了回调函数 ,将在当前脚本所有的同步任务执行完成后才会执行,所以resolved最后输出。
接下来,用Promise 完成一次网络请求
```javascript function loadImageAsync(url) { return new Promise(function(resolve, reject) { // 创建一个图片对象 const image = new Image(); // 当图像装填完毕时调用的句柄。 image.onload = function() { resolve(image); }; // 当图像装载发生错误时执行此方法 image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; // 设置图片的sc image.src = url; }); }loadImageAsync(‘https://cn.bing.com/th?id=OHR.RainierClouds_ENUS_SS1021697089_1920x1080_HD_ZH-CN170801398.jpg&rf=LaDigue_1366x768.jpg&pid=hp’).then(function (value){
console.log(‘成功’)
var body = document.getElementsByTagName(“body”)[0];
console.log(body);
body.appendChild(value);
console.log(value);
}).catch(function (value){
console.log("失败 ");
console.log(value);
})上面的代码中,使用Promise 包装了一个图片加载的异步操作 , 如果加载成功 , 就调用resolve方法,否则就调用reject 方法。 <h4>接下来,再来一个异步调用的例子</h4> ```javascript const getJson = function(url){ const promise = new Promise(function(resolve , reject){ const handler = function(){ if(this.readyState != 4){ return; } if(this.status === 200){ resolve(this.response); }else{ reject(new Error(this.statusTest)); } }; const client = new XMLHttpRequest(); client.open("GET",url); client.onreadystatechange = handler; client.responseType = 'json'; client.setRequestHeader('Accept',"application/json"); client.send(); }); return promise; } getJson("http://182.254.147.230/auth/api/ckusr?name=itrip%40163.com").then(function(json){ console.log(json); }).catch(function(error){ console.log(error); })
上面代码中 , getJson是XMLHttpRequest 对象的封装,用于发出一个针对JSON数据的Http请求,并且返回一个Promise对象,需要注意的是, 在getJSON内部,resolve 函数和reject 函数调用是都带有函数
如果调用resolve函数和reject函数带有参数, 那么他们的参数会被传递给回调函数。reject函数的的参数通常是Error对象的实例,表示抛出的错误;resolve函数除了正常的值之外,还可能是另一个Promise 实例。
const p1 = new Promise(function (resolve, reject) { // ... }); const p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上面的代码中,p1 和 p2 都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个几步操作。
注意,这是p1 的状态就会传递给p2 也就是说,p1的状态决定了p2的状态,如果p1的状态是pending那么p2的回调函数就会等待p1的状态做出改变;如果p1的状态是resolved 或者 rejected 那么p2 的回调函数将会立即执行。
const p1 = new Promise(function (resolve, reject) { console.log("p1 执行") setTimeout(() => resolve(new Error('fail')), 1000) }) const p2 = new Promise(function (resolve, reject) { console.log('p2执行'); setTimeout(() => resolve(p1), 1000) }) p2 .then(result => console.log("success",result)) .catch(error => console.log(error))
上面代码的整体意思是,p2 怎么走完全是看p1的脸色来。
注意 调用resolve或者reject并不会终结Promise的参数函数的执行
new Promise((resolve, reject) => { resolve(1); console.log(2); }).then(r => { console.log(r); }); // 2 // 1
上面的代码中,调用resolve(1) 以后。 后面的console.log(2) 还是会执行的,并且会首先打印出来, 这是因为立即resolved的Promise是在本轮事件循环末尾执行,总是晚于本轮的循环的同步认任务。
通常情况来讲, 调用resolve 或 reject 以后。promise的使命就完成了。后继操作应该放到then方法里面,而不应该直接写在resolve 或 reject 的后面, 所以,最好在他们前面加上return 语句。这样就可以确保没有意外
Promise.prototype .then
primise实例具有then 方法,也就是说,then方法是定义在原型对象上的, 它的作用是为promise 实例添加状态改变时的回调函数,前面说过,then 方法的第一个参数时resolved状态的回调函数,第二个参数是rejected的回调将函数,then 方法返回的一个新拿到Promise实例,(注意是一个新的实例,不是原来的)。因此可以采用链式写法,即then后面可以再调用另一个then方法 。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代码使用then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise(即有异步操作), 这时后一个回调函数就会等待该Pormise 对象的状态发生变化,才会被调用
getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function (comments) { console.log("resolved: ", comments); }, function (err){ console.log("rejected: ", err); });
上面的代码中,第一个then方法指定的回调函数,返回的是是另一个Promise对象,这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。并调用相应的参数。
Promise.prototype.catch()
promise.prototype.catch 方法是,then(null , rejection) 或.then(undefined,rejection )的别名,用于指定发生错误时的回调函数,如果promise 中发生了错误,会被catch()捕获 ,
在promise对象中reject方法类似于抛出异常
const promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test'); }); promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); // ok
上面代码中,Promise 在resolve语句后面, 再抛出错误,不会被捕获。等于没有抛出,因为Promise的状态一旦改变,就永久保持,不会再变了。
promise 对象的错误具有“冒泡”性质, 会一直向后传递, 直到被捕获为止, 也就是说,错误总是被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { // some code }).catch(function(error) { // 处理前面三个Promise产生的错误 });
上面代码中,一共有三个promise对象一个有getJson()产生 两个有then()产生。它们只中任何一个抛出的错误都会被chtch()捕获;
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
通常来讲,不建议第一种写法, 理由是第二种写法可以捕获前then方法 执行中的错误, 也更将近同步的写法(try/catch)建议总是使用chatch方法。而不使用then方法的第二个参数。
跟传统的try/catch代码不同的是, 如果没有使用catch() 方法指定错误的处理的回调函数,Promise对象抛出的错误不会传递到外层代码,既不会有任何反应。
const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing().then(function() { console.log('everything is great'); }); setTimeout(() => { console.log(123) }, 2000); // Uncaught (in promise) ReferenceError: x is not defined // 123
上面代码中, 函数产生了Promise 对象, 内部有语法错误, 浏览器运行到这一行,会报错,但是不会退出进程,终止脚本(通常js中如果报错会直接中止代码的运行)。2秒之后会还是会输出123 ,这就是说,Promise 内部的错误不会影响到Promise外部的代码,通俗的说法是Promise被吃掉了。
一般来讲总是建议Promise对象后面要跟catch()方法,这样就可以处理Promise内部发生的错误。catch()方法返回的还是一个Promise 对象,因此后面还可以接着调用then方法。
const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); // oh no [ReferenceError: x is not defined] // carry on
上面代码运行完catch()返回指定的回调函数,会接着运行后面那个then()方法指定的回调函数 ,如果没有报错,则会跳过catch()方法。
Promise.prototype.finally()
finally() 方法用于指定不管promise对象最后状态如何,都会执行操作,该方法是新引入的。
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代码中,不管promise 最后的状态,在执行then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise状态到底是fulfilled 还是 rejected。 这表明,finally 方法里面的操作,应该是与状态无关的,不依赖Promise 的执行结果。
Promise.all()
all()方法用于将多个Promise 实例,包装成一个Promise实例
const p = Promise.all([p1, p2, p3]);
上面的代码中,all方法接受一个数组作为参数 , p1,p2,p3 都是Promise实例,如果不是就会使用resolve方法将他们变成promise实例,再进一步处理,另外Promise.all()方法的参数可以不是数组,但必须具有Iterator,接口且返回的没测成员都是Promise 实例。
p 的状态有p1 , p2 ,p3 决定,有两种情况, 如果全是成功,p的状态才会是成功,第二只要有一个是失败,那么p的状态就是失败,并且会获取到第一个失败的例子