Promise入门详解和基本用法

 

异步调用

异步

JavaScript的执行环境是单线程

所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程。

异步模式可以一起执行多个任务

常见的异步模式有以下几种:

  • 定时器

  • 接口调用

  • 事件函数

今天这篇文章,我们重点讲一下接口调用。接口调用里,重点讲一下Promise

接口调用的方式

js 中常见的接口调用方式,有以下几种:

  • 原生ajax
  • 基于jQuery的ajax
  • Fetch
  • Promise
  • axios

多次异步调用的依赖分析

  • 多次异步调用的结果,顺序可能不同步。

  • 异步调用的结果如果存在依赖,则需要嵌套。

在ES5中,当进行多层嵌套回调时,会导致代码层次过多,很难进行维护和二次开发;而且会导致回调地狱的问题。ES6中的Promise 就可以解决这两个问题。

Promise 概述

Promise的介绍和优点

ES6中的Promise 是异步编程的一种方案。从语法上讲,Promise 是一个对象,它可以获取异步操作的消息。

Promise对象, 可以将异步操作以同步的流程表达出来。使用 Promise 主要有以下好处:

  • 可以很好地解决回调地狱的问题(避免了层层嵌套的回调函数)。

  • 语法非常简洁。Promise 对象提供了简洁的API,使得控制异步操作更加容易。

回调地狱的举例

假设买菜、做饭、洗碗都是异步的。

但真实的场景中,实际的操作流程是:买菜成功之后,才能开始做饭。做饭成功后,才能开始洗碗。这里面就涉及到了多层嵌套调用,也就是回调地狱。

Promise 的基本用法

(1)使用new实例化一个Promise对象,Promise的构造函数中传递一个参数。这个参数是一个函数,该函数用于处理异步任务。

(2)并且传入两个参数:resolve和reject,分别表示异步执行成功后的回调函数和异步执行失败后的回调函数;

(3)通过 promise.then() 处理返回结果。这里的 p 指的是 Promise实例。

代码举例如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// 第一步:model层的接口封装
const promise = new Promise((resolve, reject) => {
// 这里做异步任务(比如ajax 请求接口。这里暂时用定时器代替)
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
}); // 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promise.then(data => {
// 从 resolve 获取正常结果
console.log(data);
}).catch(data => {
// 从 reject 获取异常结果
console.log(data);
});
</script>
</body>
</html>

上方代码中,当从接口返回的数据data.retCode的值不同时,可能会走 resolve,也可能会走 reject,这个由你自己的业务决定。

promise对象的3个状态(了解即可)

  • 初始化状态(等待状态):pending

  • 成功状态:fullfilled

  • 失败状态:rejected

(1)当new Promise()执行之后,promise对象的状态会被初始化为pending,这个状态是初始化状态。new Promise()这行代码,括号里的内容是同步执行的。括号里定义一个function,function有两个参数:resolve和reject。如下:

  • 如果请求成功了,则执行resolve(),此时,promise的状态会被自动修改为fullfilled。

  • 如果请求失败了,则执行reject(),此时,promise的状态会被自动修改为rejected

(2)promise.then()方法,括号里面有两个参数,分别代表两个函数 function1 和 function2:

  • 如果promise的状态为fullfilled(意思是:如果请求成功),则执行function1里的内容

  • 如果promise的状态为rejected(意思是,如果请求失败),则执行function2里的内容

另外,resolve()和reject()这两个方法,是可以给promise.then()传递参数的。

完整代码举例如下:

 let promise = new Promise((resolve, reject) => {
//进来之后,状态为pending
console.log('111'); //这行代码是同步的
//开始执行异步操作(这里开始,写异步的代码,比如ajax请求 or 开启定时器)
if (异步的ajax请求成功) {
console.log('333');
resolve('haha');//如果请求成功了,请写resolve(),此时,promise的状态会被自动修改为fullfilled
} else {
reject('555');//如果请求失败了,请写reject(),此时,promise的状态会被自动修改为rejected
}
})
console.log('222'); //调用promise的then()
promise.then((successMsg) => {
//如果promise的状态为fullfilled,则执行这里的代码
console.log(successMsg, '成功了');
}
, (errorMsg) => {
//如果promise的状态为rejected,则执行这里的代码
console.log(errorMsg, '失败了'); }
)

基于 Promise 处理 多次 Ajax 请求(链式调用)【重要】

实际开发中,我们经常需要同时请求多个接口。比如说:在请求完接口1的数据data1之后,需要根据data1的数据,继续请求接口2,获取data2;然后根据data2的数据,继续请求接口3。

这种场景其实就是接口的多层嵌套调用。有了 promise之后,我们可以把多层嵌套调用按照线性的方式进行书写,非常优雅。

也就是说:Promise 可以把原本的多层嵌套调用改进为链式调用

代码举例:(多次 Ajax请求,链式调用)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise发送Ajax请求
*/
function queryData(url) {
var promise = new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText); // xhr.responseText 是从接口拿到的数据
} else {
// 处理异常情况
reject('接口请求失败');
}
};
xhr.responseType = 'json'; // 设置返回的数据类型
xhr.open('get', url);
xhr.send(null); // 请求接口
});
return promise;
}
// 发送多个ajax请求并且保证顺序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
// 请求完接口1后,继续请求接口2
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 请求完接口2后,继续请求接口3
return queryData('http://localhost:3000/api3');
},
error2 => {
console.log(error2);
}
)
.then(
data3 => {
// 获取接口3返回的数据
console.log(JSON.stringify(data3));
},
error3 => {
console.log(error3);
}
);
</script>
</body>
</html>

上面这个举例很经典,需要多看几遍。

return 的函数返回值

return 后面的返回值,有两种情况:

  • 情况1:返回 Promise 实例对象。返回的该实例对象会调用下一个 then。

  • 情况2:返回普通值。返回的普通值会直接传递给下一个then,通过 then 参数中函数的参数接收该值。

我们针对上面这两种情况,详细解释一下。

情况1:返回 Promise 实例对象

举例如下:(这个例子,跟上一段 Ajax 链式调用 的例子差不多)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise发送Ajax请求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText);
} else {
// 处理异常情况
reject('接口请求失败');
}
};
xhr.responseType = 'json'; // 设置返回的数据类型
xhr.open('get', url);
xhr.send(null); // 请求接口
});
}
// 发送多个ajax请求并且保证顺序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 这里的 return,返回的是 Promise 实例对象
return new Promise((resolve, reject) => {
resolve('qianguyihao');
});
},
error2 => {
console.log(error2);
}
)
.then(data3 => {
console.log(data3);
});
</script>
</body>
</html>

情况2:返回 普通值

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
基于Promise发送Ajax请求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText);
} else {
// 处理异常情况
reject('接口请求失败');
}
};
xhr.responseType = 'json'; // 设置返回的数据类型
xhr.open('get', url);
xhr.send(null); // 请求接口
});
}
// 发送多个ajax请求并且保证顺序
queryData('http://localhost:3000/api1')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData('http://localhost:3000/api2');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 返回普通值
return 'qianguyihao';
},
error2 => {
console.log(error2);
}
)
/*
既然上方返回的是 普通值,那么,这里的 then 是谁来调用呢?
答案是:这里会产生一个新的 默认的 promise实例,来调用这里的then,确保可以继续进行链式操作。
*/
.then(data3 => {
// 这里的 data3 接收的是 普通值 'qianguyihao'
console.log(data3);
});
</script>
</body>
</html>

Promise 的常用API:实例方法【重要】

Promise 自带的API提供了如下实例方法:

  • promise.then():获取异步任务的正常结果。

  • promise.catch():获取异步任务的异常结果。

  • promise.finaly():异步任务无论成功与否,都会执行。

代码举例如下。

写法1:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function queryData() {
return new Promise((resolve, reject) => {
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
} queryData()
.then(data => {
// 从 resolve 获取正常结果
console.log('接口请求成功时,走这里');
console.log(data);
})
.catch(data => {
// 从 reject 获取异常结果
console.log('接口请求失败时,走这里');
console.log(data);
})
.finally(() => {
console.log('无论接口请求成功与否,都会走这里');
});
</script>
</body>
</html>

写法2:(和上面的写法1等价)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function queryData() {
return new Promise((resolve, reject) => {
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
} queryData()
.then(
data => {
// 从 resolve 获取正常结果
console.log('接口请求成功时,走这里');
console.log(data);
},
data => {
// 从 reject 获取异常结果
console.log('接口请求失败时,走这里');
console.log(data);
}
)
.finally(() => {
console.log('无论接口请求成功与否,都会走这里');
});
</script>
</body>
</html>

注意:写法1和写法2的作用是完全等价的。只不过,写法2是把 catch 里面的代码作为 then里面的第二个参数而已。

Promise 的常用API:对象方法【重要】

Promise 自带的API提供了如下对象方法:

  • Promise.all():并发处理多个异步任务,所有任务都执行成功,才能得到结果。

  • Promise.race(): 并发处理多个异步任务,只要有一个任务执行成功,就能得到结果。

下面来详细介绍。

Promise.all() 代码举例

代码举例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
封装 Promise 接口调用
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常结果
resolve(xhr.responseText);
} else {
// 处理异常结果
reject('服务器错误');
}
};
xhr.open('get', url);
xhr.send(null);
});
} var promise1 = queryData('http://localhost:3000/a1');
var promise2 = queryData('http://localhost:3000/a2');
var promise3 = queryData('http://localhost:3000/a3'); Promise.all([promise1, promise2, promise3]).then(result => {
console.log(result);
});
</script>
</body>
</html>

Promise.race() 代码举例

代码举例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
/*
封装 Promise 接口调用
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常结果
resolve(xhr.responseText);
} else {
// 处理异常结果
reject('服务器错误');
}
};
xhr.open('get', url);
xhr.send(null);
});
} var promise1 = queryData('http://localhost:3000/a1');
var promise2 = queryData('http://localhost:3000/a2');
var promise3 = queryData('http://localhost:3000/a3'); Promise.race([promise1, promise2, promise3]).then(result => {
console.log(result);
});
</script>
</body>
</html>

promise入门基本使用的更多相关文章

  1. Promise入门到精通(初级篇)-附代码详细讲解

    Promise入门到精通(初级篇)-附代码详细讲解 ​     Promise,中文翻译为承诺,约定,契约,从字面意思来看,这应该是类似某种协议,规定了什么事件发生的条件和触发方法. ​     Pr ...

  2. 一.Promise入门准备阶段

    一.Promise入门准备阶段 1.区别实例对象呵函数对象 2.两种类型的回调函数(同步与异步) 2.1 同步回调 2.2 异步回调 3.JS的error处理 3.1 错误的类型 3.2 错误处理与错 ...

  3. Javascript Promise入门

    是什么? https://www.promisejs.org/ What is a promise? The core idea behind promises is that a promise r ...

  4. promise入门demo

    <!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...

  5. javaScript Promise 入门

    Promise是JavaScript的异步编程模式,为繁重的异步回调带来了福音. 一直以来,JavaScript处理异步都是以callback的方式,假设需要进行一个异步队列,执行起来如下: anim ...

  6. Promise 入门与使用

    Tags: ECMAScript6 参考资料 promises-book Promise对象 we-have-a-problem-with-promises Promise最初被提出是在 E语言中, ...

  7. Promise入门详解

    异步调用 异步 JavaScript的执行环境是单线程. 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它 ...

  8. ES6中Promise的入门(结合例子)

    一.Promise的前言 解决回调地狱 //以往回调方式 函数1(function(){ //代码执行...(ajax1) 函数2(function(){ //代码执行...(ajax2) 函数3(f ...

  9. 细说Promise

    一.前言 JavaScript是单线程的,固,一次只能执行一个任务,当有一个任务耗时很长时,后面的任务就必须等待.那么,有什么办法,可以解决这类问题呢?(抛开WebWorker不谈),那就是让代码异步 ...

随机推荐

  1. WPF自定义控件一:StackPanel 控件轮播

    实现效果 带定时器的轮播图 using引用 using System.Windows; using System.Windows.Controls; using System.Windows.Mark ...

  2. awk-06-常用内置函数

    常用内置函数 示例 1.int 2.sqrt 3.rand rand()并不是每次运行都会产生一个随机数,会一直保持不变 所以需要srand() 函数一起 但是还是有很大的几率会生成一样 4.asor ...

  3. 单片机学习(八)点阵LED的使用

    目录 一.点阵LED的理论知识 1. 点阵屏的打开方式 2. LED点阵简介 3. 点阵LED的结构和操作方式 4. 74HC595模块 PPT上的简介 串行转并行的工作原理 二.编码实现 1. C5 ...

  4. Linux部署达梦数据库(完全版)

    环境准备 数据库下载去http://www.dameng.com 1.下载好达梦数据库iso上传(通过xftp)到你自己存在的目录(/opt) 2.将你上传的iso文件挂载到其它的目录下面(mount ...

  5. 内存吞金兽(Elasticsearch)的那些事儿 -- 常见问题痛点及解决方案

    1.大数据量的查询效率如何保证: 查询的流程:往 ES 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去 最佳的情况 ...

  6. 题解 y

    传送门 考场上写的记忆化不够快--和暴力一个分 如果题面里有提到类似「从点1出发」的字样,特别注意点1根本就没有连边的情况 这题写记忆化的时候是想搜出所有可能的组合, 那么对于一个点u,剩余深度为d时 ...

  7. WPF 饼状图,柱形图,折线图 (2 折线图)

    折线图在柱形图的基础上,做了一些修改.大概效果和用法如下. X轴和Y轴的刻度,使用用了Path的Figures属性,绘制多条Figure+LineSegment完成. 同时,由于折线图很可能会画多条线 ...

  8. 【springcloud】Eureka 常用配置解析

    转自:https://www.cnblogs.com/zyon/p/11023750.html 1. 配置项解析 1.1 通用配置 # 应用名称,将会显示在Eureka界面的应用名称列 spring. ...

  9. WPF---数据绑定(二)

    一.绑定到非UI元素 上篇中,我们绑定的数据源均是派生自UIElement的WPF元素.本篇描述的绑定数据源是一个我们自定义的普通的类型. 注:尽管绑定的数据源可以是任意类型的对象,但Path必须总是 ...

  10. 【java虚拟机】jvm调优原则

    转自:https://www.cnblogs.com/xiaopaipai/p/10522794.html 合理规划jvm性能调优 JVM性能调优涉及到方方面面的取舍,往往是牵一发而动全身,需要全盘考 ...