【ES6】Promise 对象

本文深入解析Promise的基本用法,包括.then(), .catch(), .finally(),并展示了静态方法如.resolve(), .reject(), .all(), .any(), .race(), .allSettled()的实践案例。通过Ajax操作演示了如何优雅处理异步操作,提升代码可读性和维护性。
摘要由CSDN通过智能技术生成

基本介绍

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

  • new Promise 构造器返回的 promise 对象具有以下内部属性:

  • state —— 最初是 "pending"

    • 然后在 resolve 被调用时变为 "fulfilled"
    • 或者在 reject 被调用时变为 "rejected"
  • result —— 最初是 undefined

    • 然后在 resolve(value) 被调用时变为 value
    • 或者在 reject(error) 被调用时变为 error
      在这里插入图片描述
let p = new Promise((resolve, reject)=>resolve(88))
console.log(p)
let b = new Promise((resolve, reject)=>reject("报错"))
console.log(b)

在这里插入图片描述

  • Promise 优缺点:
1. 对象的状态不受外界影响:
- 它有三种状态:`pending`(进行中)、`fulfilled`(已成功)和`rejected`(已失败)
- 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 状态改变情况:从 `pending` 变为 `fulfilled` 和 从 `pending` 变为 `rejected`
- 改变情况发生,状态就不会再变,会一直保持这个结果,这时就称为 `resolved`(已定型)
- 如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果
1. 一旦新建它就会立即执行,无法中途取消 Promise
2. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
  • 为了行文方便,resolve 统一只指 fulfilled 状态,reject 统一只指 rejected 状态。

基本用法

  • Promise 参数:
1. Promise 的两个参数分别是resolve 和 reject 函数
2. 可以用`then`方法分别指定`resolved`状态和`rejected`状态的回调函数
3. `resolve` 函数的作用是:
在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
4. `reject` 函数的作用是:
在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
5. `then`方法可以接受两个回调函数作为参数:
第一个回调函数是 Promise 对象的状态变为 resolved 时调用
第二个回调函数是 Promise 对象的状态变为 rejected 时调用
  • 语法:
new Promise(function(resolve,reject){
	// resolve 表示成功的回调
	// reject 表示失败的回调	
}).then(function (res){
	// 成功的函数
},function(err){
	// 失败的函数
})
  • 下面是一个用Promise对象实现的 Ajax 操作的例子
/*
	getJSON是对 XMLHttpRequest 对象的封装:
	用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象
*/ 
const getJSON = function(url) {
  // 在 getJSON 内部,resolve 函数和 reject 函数调用时,都带有参数
  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.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

对象方法

1. then()

  • then 方法的作用是为 Promise 实例添加状态改变时的回调函数
第一个参数是 resolved 状态的回调函数
第二个参数是 rejected 状态的回调函数
  • then方法返回的是一个新的Promise实例
  • 因此可以采用链式写法,即then方法后面再调用另一个then方法
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数
});

2. catch()

  • catch()方法是.then(null, rejection).then(undefined, rejection)的别名
  • 用于指定发生错误时的回调函数
getJSON('/posts.json').then(function(posts) {
  // 状态变为resolve,调用then()方法里的函数
}).catch(function(error) {
  // 处理 getJSON 和 前一个 then 的回调函数运行时发生的错误
  console.log('发生错误!', error);
});
  • 写法对比:
// 不推荐使用
// 建议总是使用catch()方法,而不使用then()方法的第二个参数
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// 推荐使用,可以捕获前面then方法执行中的错误
// 更接近同步的写法(try/catch)
// 如果没有使用catch()方法指定错误处理的回调函数,
// Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

3. finally()

  • finally()方法是 ES2018 引入标准的,即 ES9
  • 用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})	// 成功执行的回调函数
.catch(error => {···})	// 失败执行的回调函数
.finally(() => {···});	// 最后执行的回调函数
  • finally方法的回调函数不接受任何参数,因为里面的操作与状态无关,不依赖于 Promise 的执行结果。
  • finally方法总是会返回原来的值
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

静态方法

Promise 并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态

1、成功状态:Promise.resolve()

Promise.resolve()方法:可以实例化一个解决fulfilled状态的Promise(将现有对象转为 Promise 对象)

// 将 jQuery 生成的deferred对象,转为一个新的 Promise 对象
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
  • 下面两个 Promise实例实际上是一样的
const p = new Promise(resolve => resolve('返回的结果'))
// 等价于
const p = Promise.resolve('返回的结果')
  • Promise.resolve()方法的参数
1. 参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2. 参数是具有 then() 方法的对象
将这个对象转为 Promise 对象,然后就立即执行里面的 then()方法
3. 参数不是具有 then()方法的对象,或根本就不是对象
返回一个新的 Promise 对象,状态为resolved
4. 不带有任何参数
直接返回一个resolved状态的 Promise 对象。

2、失败状态:Promise.reject()

Promise.reject()方法:可以实例化一个解决rejected状态的Promise(将现有对象转为 Promise 对象),并抛出一个错误(这个错误不能通过try/catch捕获,只能通过拒绝处理程序捕获)

  • 下面两个 Promise实例实际上是一样的
const p = new Promise((resolve, reject) => reject('抛出的错误'))
// 等同于
const p = Promise.reject('抛出的错误');
  • Promise.reject() 方法的参数,会被传递给实例的回调函数
1. 示例 1// 生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行
const p = Promise.reject('出错了');

p.then(null, function (s) {
  console.log(s)	// 出错了
});

2. 示例 2// 此时参数是一个字符串
Promise.reject('错误').catch(e => {
	// 后面catch()方法的参数e就是这个字符串
	console.log(e === '错误')	// true
})

3、全胜一败:Promise.all()

Promise.all()方法:用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);
//  接受一个数组作为参数,p1、p2、p3都是 Promise 实例
// 如果不是,可以用Promise.resolve方法,将参数转为 Promise 实例
// 参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
  • p 的状态:
1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled
此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected
此时第一个被reject的实例的返回值,会传递给p的回调函数
  • 注意,如果作为参数的 Promise 实例:
  • 自己定义了 catch 方法,就不会触发 Promise.all() 的 catch 方法
  • 自己未定义 catch 方法,就会调用 Promise.all() 的 catch 方法
// 生成一个Promise对象的数组,包含 6 个 Promise 实例
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // 只有这 6 个实例的状态都变成 fulfilled 调用这个回调函数
}).catch(function(reason){
  // 其中有一个变为 rejected 调用这个回调函数,且6 个 Promise 实例中没有自带catch
});

4、一胜全败:Promise.any()

  • Promise.any():ES2021 引入
  • 该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {  // 只要有一个 fetch() 请求成功,包装实例就会变成fulfilled状态
  console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败,包装实例就会变成rejected状态
  console.log(error);
});
  • Promise()与await命令结合使用的例子
// Promise.any()方法的参数数组包含三个 Promise 操作
const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
// 其中只要有一个变成fulfilled,Promise.any()返回的 Promise 对象就变成fulfilled
// 如果所有三个操作都变成rejected,那么await命令就会抛出错误
try {
  const first = await Promise.any(promises);
  console.log(first);
} catch (error) {
  console.log(error);
}
  • Promise.any()抛出的错误,是一个 AggregateError 实例,相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results); // [-1, Infinity]
});

5、最快结束:Promise.race()

Promise.race()方法:同样是将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.race([p1, p2, p3]);
// Promise.race()方法的参数与Promise.all()方法一样
  • p 的状态:
1. 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
2. 那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
3. 意思就是说,p1, p2, p3里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
  • 一般情况下用不到这个方法
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

6、全部结束:Promise.allSettled()

  • Promise.allSettled():ES2020 引入,Settled(固定的),包含了”fulfilled“和”rejected“两种情况。
  • Promise.allSettled()方法:用来确定一组异步操作是否都结束了(不管成功或失败)。

  • Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更
// 数组promises包含了三个请求
const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];

await Promise.allSettled(promises);
// 只有等到这三个请求都结束了(不管请求成功还是失败),removeLoadingIndicator()才会执行
removeLoadingIndicator();
  • 该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
// allSettledPromise,状态只可能变成 fulfilled
const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  // 它的回调函数接收到的参数是数组results
  console.log(results);
});
// 该数组的每个成员都是一个对象,对应传入Promise.allSettled()的数组里面的两个 Promise 对象。
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
  • results的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果。
/*
	成员对象的status属性的值只可能是字符串fulfilled或字符串rejected
	用来区分异步操作是成功还是失败
	
	如果是成功(fulfilled),对象会有value属性,
	如果是失败(rejected),会有reason属性,
	对应两种状态时前面异步操作的返回值。
*/
// 异步操作成功时
{status: 'fulfilled', value: value}

// 异步操作失败时
{status: 'rejected', reason: reason}
  • 返回值的用法例子
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);

// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');

// 过滤出失败的请求,并输出原因
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

案例分析

  • promise 是承诺的意思,是一个专门用来解决异步 回调地狱 的问题

  • 我们的每一个异步事件,在执行的时候都会有三个状态:执行中、成功、失败

  • 因为他包含了成功的回调函数

  • 所以我们就可以使用 promise 来解决多个 AJAX 发送的问题

  • 设置3个PHP文件

# a.php
<?php
	echo json_encode(array('n1'=>12,'n2'=>56));
?>
# b.php
<?php
	$n1 = $_GET['n1'];
	$n2 = $_GET['n2'];
	$result = $n1+$n2;
	echo json_encode(array('n1'=>$result,'n2'=>4));
?>
# c.php
<?php
	$n1 = $_GET['n1'];
	$n2 = $_GET['n2'];
	$result = $n1*$n2;
	echo json_encode(array('n1'=>$result,'n2'=>78));
?>
  • ajax发送请求
/* 
    需求:
        1 发送一个请求到a.php
            ==> 能得到一个结果
            ==> 有两个数字
        2 在第一个请求结束之后,发送第二个请求到b.php
            ==> 需要携带第一个请求返回的结果:  "./b.php?n1=12&n2=56"
            ==> 能得到一个结果是两个数字的和,和另外一个数字
        3 在第二个请求结束之后,发送第三个请求到c.php
            ==> 需要携带第二个请求返回的结果:  "./c.php?n1=68&n2=4"
            ==> 能得到一个结果是两个数字的乘积,和另外一个数字
*/
new Promise(function (resolve) {
	// 1 发送一个请求到a.php
	var xhr = new XMLHttpRequest();
	xhr.open('get','./a.php');
	xhr.onreadystatechange = function(){
		 if(xhr.readyState==4&&xhr.status==200){
			resolve(xhr.response);	
		}
	}
	xhr.send();
}).then(function(data){
	// 第一个请求a.php成功的回到函数
	// data:xhr.response
	var json = JSON.parse(data);
	return new Promise(function(resolve){
		// 2 发送第二个请求到b.php
		var xhr = new XMLHttpRequest();
		xhr.open('GET',`./b.php?n1=${json.n1}&n2=${json.n2}`);
		xhr.onreadystatechange = function(){
			if(xhr.readyState==4&&xhr.status==200){
				// 要执行成功的回调函数
				resolve(xhr.response);
			}
		}
		xhr.send();
	})
}).then(function(data){
	// data就是第二次请求到b.php返回的内容:xhr.response
	var json = JSON.parse(data);
	return new Promise(function(resolve){
		// 3 发送第三个请求到c.php
		var xhr = new XMLHttpRequest();
		xhr.open('get',`./c.php?n1=${json.n1}&n2=${json.n2}`);
		xhr.onreadystatechange = function(){
			if(xhr.status==200&&xhr.readyState==4){
				// 执行成功的回调函数
				resolve(xhr.response);
			}
		}
		xhr.send();
	})
}).then(function(data){
	// data就是第三次请求到的c.php返回的内容:xhr.response
	console.log(data);	// {"n1":272,"n2":78}
})
  • 封装函数:ajax-promise.js
// GET 方式发送的请求
function getSend(url){
	var p = new Promise(function(resolve){
		var xhr = new XMLHttpRequest();
		xhr.open('GET',url);
		xhr.onreadystatechange = function(){
			if(xhr.readyState==4&&xhr.status==200){
				resolove(xhr.responseText);
			}
		}
		xhr.send();
	});
	return p;
}
// POST 方式发送的请求
function postSend(url,params){
	var p = new Promise(function(resolve){
		var xhr = new XMLHttpRequest();
		xhr.open('POST',url);
		xhr.onreadystatechange = function(){
			if(xhr.readyState==4&&xhr.status==200){
				resolve(xhr.responseText);
			}
		}
		xhr.setRequsetHeader('content-type','application/x-www-form-urlencoded');
		xhr.send(params);
	});
	return p;
}
  • 利用封装函数发送请求
// 1 发送一个请求到a.php
getSend('./a.php').then(function(data){
	var json = JSON.parse(data);
	// 2 发送一个请求到b.php
	return getSend(`./b.php?n1=${json.n1}&n2=${json.n2}`);
}).then(function(data){
	var json = JSON.parse(data);
	// 3 发送一个请求到c.php
	return getSend(`./c.php?n1=${json.n1}&n2=${json.n2}`);
}).then(function(data){
	console.log(data);	// {"n1":272,"n2":78}
});
  • 这个时候,我们的代码已经改观了很多了
  • 基本已经可以维护了
  • 但是对于一个程序员来说,这个样子是不够的
  • 我们还需要更加的简化代码,怎么简单怎么来
  • 所以我们就需要用到一个 es8 的语法:async/await

  • async/await 是回调地狱的终极解决方案
  • 把异步代码写的看起来像同步代码
  • 只要是一个 promiser 对象,那么我们就可以使用 async/await 来书写
async function fn() {
	// 1 发送一个请求到a.php
	var data = await getSend('./a.php');
	var json = JSON.parse(data);
	// 2 发送第二个请求到b.php
	data = await getSend(`./b.php?n1=${json.n1}&n2=${json.n2}`);
	json = JSON.parse(data);
	// 3 发送第三个请求到c.php
	data = await getSend(`./c.php?n1=${json.n1}&n2=${json.n2}`);
	console.log(data);	// {"n1":272,"n2":78}
}
fn();
  • 这样的异步代码写的就看起来像一个同步代码了
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ES6(ECMAScript 2015)引入了Promise对象,它是一种异步编程的解决方案。当一个异步操作完成后,Promise对象会返回一个代表操作结果的值,而不是像传统方式那样使用回调函数。 Promise对象的then方法用于指定操作成功和失败时的回调函数。而且可以将then方法连续使用,形成链式调用。当一个Promise对象的then方法返回另一个Promise对象时,后续的then方法都会等待该Promise对象的状态改变。 链式调用的好处在于减少了回调函数嵌套的层数,提高了代码的可读性和可维护性。通过then方法的链式调用,可以构建一串异步操作,使得代码逻辑更加清晰。 例如: ``` function getJSON(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error('Unable to fetch data')); } } }; xhr.send(); }); } getJSON('/data/user.json') .then(user => { return getJSON(`/data/profile/${user.id}.json`); }) .then(profile => { console.log(profile); }) .catch(error => { console.error(error); }); ``` 上述代码展示了一个获取用户信息和个人资料的异步操作,其中getJSON函数返回一个Promise对象。通过then方法的链式调用,可以先获取用户信息,然后再获取个人资料。如果有任何一个异步操作失败,则会进入catch回调函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值