ES6 Promise详解
前言
本文主要是对Promise本身的用法做一个全面解析而非它的原理实现,如果你对Promise的用法还不是很熟悉或者想加深你对Promise的理解,我相信这篇文章一定会帮到你。
首先让我们先了解一下JavaScript为什么会引入Promise
回调地狱
让我们先看这样一段代码,JQuery中ajax请求:
$.ajax({
url: "url1",
data: {},
success(res1) {
//获取到第一个数据
console.log(res1);
//根据第一个数去去获取第二个数据
$.ajax({
url: "url2",
data: {
query: res1.xxx,
},
success(res2) {
//获取到第二个数据
console.log(res2);
//根据第二个数去去获取第三个数据
$.ajax({
url: "url3",
data: {
query: res2.xxx,
},
success(res3) {
//获取到第三个数据
console.log(res3);
//...
},
});
},
});
},
error(err) {
console.log(err);
},
});
我们会发现这些回调一层又一层,这就被称为回调地狱(callback hell),尤其业务逻辑复杂的时候这些回调就会变得难以维护。于是Promise就出现了。我们再看一个使用promise封装的axios请求:
axios
.get(url1, {})
.then((res1) => {
//获取到第一个数据
console.log(res1);
//根据第一个数去去获取第二个数据
return axios.get(url2, { query: res1.xxx });
})
.then((res2) => {
//获取到第一个数据
console.log(res2);
//根据第二个数去去获取第三个数据
return axios.get(url3, { query: res2.xxx });
})
.then((res3) => {
//获取到第三个数据
console.log(res3);
//...
})
.catch((err) => {
console.log(err);
});
通过对比我们会发现Promise解决了传统的回调函数的回调地狱问题,使得业务逻辑显得更加清晰了。接下来我们就开始正式介绍Promise了。
概述
Promise是现代异步编程的基础,在Promise返回给我们的时候操作其实还没有完成,但Promise对象可以让我们操作最终完成时对其进行处理,无论成功还是失败。
Promise的返回有三种状态分别是等待(pending), 成功(fulfilled),拒绝(rejected),我们看以下示例(后续我们将用延时器setTimeout来代表我们的异步操作)
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1000);
});
console.log(promise1);
此时我们可以看到我们获取的Promise是pending(等待的状态)。
同样当我们一秒钟过后再去获取Promise
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
setTimeout(() => {
console.log(promise1);
}, 1000);
它得到的就是成功(fulfilled)状态
然后我们将resolve换成reject
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 1000);
});
setTimeout(() => {
console.log(promise1);
}, 1000);
它得到的便是拒绝(rejected)状态,同时给你抛出了一个错误
基本使用
Promise构造函数只有一个函数作为参数,这个函数会在一个Promise被实例化出来后会被立即执行
new Promise((resolve, reject) => {
console.log(1);
});
console.log(2);
此时输出的结果是:1 2
Promise接收的函数有两个参数,分别是resolve和reject,其中resolve代表一切正常的时候所调用的函数,reject则代表我们程序异常的时候所调用的函数。resolve函数传入的参数用于向下一个then传递一个值,而reject函数传入的参数则会被.catch捕捉。而Promise.finally则是在Promise状态完成后触发的一个回调,即无论是resolve还是reject都会触发
//成功示例
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功的值");
});
})
.then((res) => {
console.log(res); //成功的值
})
.catch((err) => {
//不会触发
console.log(err);
})
.finally(() => {
console.log("end"); //end
});
//失败示例
new Promise((resolve, reject) => {
setTimeout(() => {
reject("失败的的值");
});
})
.then((res) => {
//不会触发
console.log(res);
})
.catch((err) => {
console.log(err); ////失败的值
})
.finally(() => {
console.log("end"); //end
});
以上便是Promise的基本使用,但是只掌握它的基本使用可不行,我们还需要对其更深入的研究
链式调用
当我们使用Promise的时候,只要我们在.then的回调函数中返回一个成功状态(resolve)的Promise,则在下一个.then的回调函数中便可获取到这个成功函数(resolve)的参数,基于这个特性便有了Promise的链式调用。
new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值1");
})
.then((res) => {
console.log(res); //成功的值1
return new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值2");
});
})
.then((res) => {
console.log(res); //成功的值2
return new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值3");
});
})
.then((res) => {
console.log(res); //成功的值3
//以此类推...
});
我们可以对其进行简写,比如
new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值");
});
可以简写为
Promise.resolve('成功的值')
所以我们的链式调用可以简写为
new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值1");
})
.then((res) => {
console.log(res); //成功的值1
return Promise.resolve("成功的值2");
})
.then((res) => {
console.log(res); //成功的值2
return Promise.resolve("成功的值3");
})
.then((res) => {
console.log(res); //成功的值3
//以此类推...
});
同样的reject的简写方式也和resolve一样
new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
reject("失败的值");
});
//简写为
Promise.reject('失败的值')
一般我们在实际项目中一般会这样写
...
//网络请求中获取到数据后
if(xxx){
//成功
return Promise.resolve('请求的值')
}
return Promise.reject('失败原因')
...
其实.then中也会自动返回Promise的封装,也就是说这个链式调用我们可以直接这样写
new Promise((resolve, reject) => {
//这里一般会有一个网络请求或其它异步操作
resolve("成功的值1");
})
.then((res) => {
console.log(res); //成功的值1
return "成功的值2";
})
.then((res) => {
console.log(res); //成功的值2
return "成功的值3";
})
.then((res) => {
console.log(res); //成功的值3
//以此类推...
});
以上便是Promise的链式调用,Promise的链式调用一般用于这些步骤间有先后顺序的操作,比如开头举的例子,需要使用前一个接口请求的数据作为参数去请求另一个接口的情形。
Promise中的all函数
在实际项目中你是否遇到过这样一个情况:你有A、B、C三个接口(或则更多),C接口的参数需要用到A和B两个接口的结果值,此时你为怎么做?
- 做法1
先请求A接口再请求B接口最后再根据AB接口的结果去请求C接口
new Promise((resolve, reject) => {
//请求A接口,这里用setTimeout模拟请求
setTimeout(() => {
resolve("A的结果");
}, 100);
})
.then((res) => {
//根据A结果请求B接口
setTimeout(() => {
return "B的请求结果";
}, 100);
})
.then((res) => {
//根据A和B结果请求C接口
setTimeout(() => {
console.log("C的请求结果");
}, 100);
})
.catch((err) => {
//这里暂不做错误考虑
});
这种写法逻辑上是没问题的,但是B和A的请求之间是完全没有交集的,而浏览器的http请求是可以同时发起多个请求的,所以这种写法很明显增加了接口请求时间
- 做法2
在每个请求结束后都去调用请求C的函数,在这个函数中判断两个请求的数据是否都获取到了,然后再进行处理
let isResultA = false;
let isResultB = false;
//请求A接口,这里用setTimeout模拟请求
setTimeout(() => {
isResultA = true;
getC()
}, 100);
//请求B接口,这里用setTimeout模拟请求
setTimeout(() => {
isResultB = true;
getC()
}, 100);
function getC() {
if (isResultA && isResultB) {
//根据A和B的结果请求C接口数据
setTimeout(() => {
console.log("C的请求结果");
}, 100);
}
}
很显然这种在写法上是很麻烦的,所以Promise提供了all方法
- 做法3
Promise.all接收一个iterable类型(Array,Map,Set 都属于 ES6 的 iterable 类型),可以放多个Promise实例,最后.then中获得的是这些输入的Promise的resolve回调的结果数组。同时只要任何一个输入的Promise的reject回调执行或者输入不合法的Promise就会立即抛出错误
Promise.all([
new Promise((resolve, reject) => {
//请求A接口,这里用setTimeout模拟请求
setTimeout(() => {
resolve("A的结果");
}, 2000);
}),
new Promise((resolve, reject) => {
//请求B接口,这里用setTimeout模拟请求
setTimeout(() => {
resolve("B的结果");
}, 1000);
}),
])
.then((res) => {
console.log(res[0]); //A的结果
console.log(res[1]); //B的结果
//根据A和B的结果请求C接口数据
setTimeout(() => {
console.log("C的请求结果");
}, 100);
})
.catch((err) => {
console.log(err);
});
Promise中的race函数
Promise.race方法返回一个promise,一旦迭代器中的某个promise完成,返回的promise就会被完成。简单来说就是它接收的promise实例中谁快就用谁的结果,不管你的结果是resove的还是reject
Promise.race([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("结果1");
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("结果2");
}, 500);
}),
new Promise((resolve, reject) => {
//请求B接口,这里用setTimeout模拟请求
setTimeout(() => {
reject("结果3");
}, 100);
}),
])
.then((res) => {
//不会触发
console.log(res);
})
.catch((err) => {
console.log(err); //结果3
});
上面示例很显然第三个Promise示例最先返回结果,所以Promise.race便使用了第三个Promise的结果
Promise中的any函数
Promise.any函数它也接收一个Promise实例的可迭代对象,只要其中的一个promise实例成功,就返回那个已经成功的promise,只有所有的promise实例都失败才会返回失败的(reject)的数组
Promise.any([
new Promise((resolve, reject) => {
setTimeout(() => {
reject("结果1");
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject("结果2");
}, 500);
}),
new Promise((resolve, reject) => {
//请求B接口,这里用setTimeout模拟请求
setTimeout(() => {
reject("结果3");
}, 100);
}),
])
.then((res) => {
//不会触发
console.log(res);
})
.catch((err) => {
console.log(err); //AggregateError: All promises were rejected
});
这个函数适用的场景可能不是很多,在这里我大概想到的一个场景就是:有三个接口A,B,C,这三个接口很不稳定但是它们返回的成功结果都一样,所以我们需要对这三个接口进行同时请求,只要它们其中有一个接口返回成功,那么我们便用这个接口的值。所以这三个接口只要有一个可用我们便可拿到想要的结果
async和await
async和await其实就是promise的语法糖形式,它可以让我们的异步代码包装成同步的形式理解。await顾名思义就是等待的意思,它必须使用在一个async的函数中,await后面跟的是一个实例化Promise,它返回的值则是这个Promise成功返回的 resolve 状态值。其实它的用法很简单,如下
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("结果");
}, 1000);
});
};
const getData = async () => {
const res = await promiseFun();
console.log(res);//结果
};
getData();
如果我们把文章开头的axios请求例子改为async,await的形式它将会是这个样子
const getAxiosData = async () => {
try {
const res1 = await axios.get(url1, {});
const res2 = await axios.get(url2, { query: res1.xxx });
const res3 = await axios.get(url2, { query: res2.xxx });
console.log(res3);
} catch (err) {
console.log(err);
}
};
getAxiosData();
此时的代码逻辑看起来就会清晰很多
写在最后
Promise的大致用法基本也就介绍完了,其实Promise还涉及到另一个方面的知识事件循环(Event Loop) 还有宏任务微任务等,由于篇幅原因,这部分我会抽时间单独写一篇关于这方面的文章。同时如果你发现文中有错误或不妥的地方欢迎指出,一定及时修改,感谢~
创作不易,你的点赞就是我的动力!如果感觉这篇文章对你有所帮助的话就请点个赞吧,感谢orz
ES6 Promise详解的更多相关文章
- ES6 Promise 详解
一.概念 Promise,从语法上来讲,它是一个对象,是一个构造函数,可以获取 异步操作 的信息. 简单来讲,就是用同步的方式写异步代码,用来解决回调问题. 二.特点 Promise 对象有两个特点: ...
- es6语法详解
什么是ECMAScript? ECMAScript是浏览器脚本语言的规范,而我们熟知的js语言,如JavaScript则是规范的具体实现.es6就好比Java的jdk. 一.es6语法详解:let声明 ...
- Es6主要特征详解
一.简介 本文将对es6的最佳特性进行分享和讲解.es6也称ES6/ECMAScript2015,在2015年诞生,但是目前实际开发中还很多用的是ES5(2009年),原因就是很多的浏览器不支持新的语 ...
- angular $q promise详解
前言 通过本文,你大概能清楚angular promise是个啥,$q又是个啥,以及怎么用它.这里咱们先灌输下promise的思想. 下面写的全是废话,一些看着高逼格其实没什么大作用的概念,想知道$q ...
- ES6中的Promise详解
Promise 在 JavaScript 中很早就有各种的开源实现,ES6 将其纳入了官方标准,提供了原生 api 支持,使用更加便捷. 定义 Promise 是一个对象,它用来标识 JavaScri ...
- ES6 中 Promise 详解
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息.Promise 提供统一的 API ...
- ES6中Promise详解
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息. Promise 提供统一的 AP ...
- ES6新增语法(五)——Promise详解
Promise介绍 promise是一个对象,从它可以获取异步操作的消息.有all.race.reject.resolve这几个方法,原型上有then.catch等方法. Promise的两个特点: ...
- js中的promise详解
一 概述 Promise是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件.ES6统一了用法,并原生提供了Promise对象.作为对象,Promise有一下两个特点: (1)对象的 ...
随机推荐
- 《Mybatis 手撸专栏》第10章:使用策略模式,调用参数处理器
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你这代码写的,咋这么轴呢! 说到轴,让我想起初中上学时老师说的话:"你那脑 ...
- 《HALCON数字图像处理》第一、二章笔记
目录 第一章 绪论 1.1 图像和图像处理 1.1.1 图像 1.1.2 数字图像 1.1.3 图像处理及其发展过程 1.2 数字图像处理的步骤和方法 1.3 数字图像处理系统的硬件组成 1.4 数字 ...
- Java随谈(六)## 我们真的理解 Java 里的整型吗?
我们真的理解 Java 里的整型吗 整型是我们日常生活中最常用到的基础数据类型,看这篇文章之前,我想问: 我们真的像自己认为的那么理解 Java 内的整型吗? 也许看完本篇文章你就有自己的答案. C ...
- ACM-由数据范围反推算法复杂度以及算法内容
一般ACM或者笔试题的时间限制是1秒或2秒. 在这种情况下,C++代码中的操作次数控制在 \(10^7\) 为最佳. 下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择: 数据范围 算法选择 ...
- linux安装源码包指定安装目录
当下载完一个源码包并且解压后 文件夹下会有一个重要的文件configure configure 文件是一个可执行的脚本文件,它将检查目标系统的配置和可用功能,比如一些检查依赖或者启用禁用一些模块,它有 ...
- 几种常见的DoS攻击
DoS为Denial of Service的简称,意思是拒绝服务.DoS攻击是一种使被攻击者无法正常提供服务的攻击.常见的攻击方式有以下几种类型: LAND Local Area Network ...
- SAP -熟练使用T-Code SHD0
SHD0 业务顾问和开发顾问都非常熟悉的一个T-Code, 如果能合理使用它,可以省去许多增强和程序修改工作. 当我需要时,我在这里找不到任何相关文档,这就是为什么我想借此机会向我们自己的SCN提供内 ...
- jenkins页面一直在Please wait while Jenkins is getting ready to work ...
原因:因为访问官网太慢.我们只需要换一个源,不使用官网的源即可. 1.找到jenkins工作目录 find / -name *.UpdateCenter.xml 2.修改文件中的url,随后重启就行了 ...
- Java判断字符串是否为金额
public static void main(String[] args) { String aa = "5632.2"; //小数点前后是数字即可,无小数点后数据也ok Sys ...
- docker容器内修改文件
1.找到容器对应的ID 使用docker ps命令找到对应的镜像id 2.根据容器id进入到对应文件夹 执行命令:docker exec -it 镜像id /bin/bash 3.进入对应目录(以My ...