文章目录
前言
为学习axios打基础。本尚硅谷Web前端Promise教程从入门到精通系列博客内容介绍。
Promise是ES6引入的进行异步编程的新的解决方案,从语法上说它是一个构造函数,可以封装异步的任务,并且可以对结果进行处理。Promise最大的好处在于可以解决回调地狱的问题,并且在指定回调和处理错误方面更加灵活,在Web或者App项目中应用十分广泛,无论前端还是后端都可以看到Promise的身影,且是现在面试的高频题目,想进入一线大厂必须掌握Promise内部运行的原理。
尚硅谷Web前端Promise教程从入门到精通系列博客主要包含以下五部分:Promise 介绍与基本使用、Promise API、Promise 关键问题、Promise 自定义封装、async 与await。
Promise 介绍与基本使用
1.Promise是什么?
1.1 理解
1.1.1 抽象表达:
- Promise 是一门新的技术(ES6规范)
- Promise 是JS 中进行异步编程的新解决方案
备注:旧方案是单纯使用回调函数
note:异步编程包括但不限于:文件操作、数据库操作、AJAX、定时器
1.1.2 具体表达:
- 从语法上来说: Promise是一个构造函数
- 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
1.2. 为什么要用Promise?
1.2.1. 指定回调函数的方式更加灵活
- 旧的: 必须在启动异步任务前指定
- promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
note:回调函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。
1.2.2. 支持链式调用, 可以解决回调地狱问题
- 什么是回调地狱? 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件
- 回调地狱的缺点? 不便于阅读、不便于异常处理
- 解决方案? promise 链式调用
- 终极解决方案? async/await
2.Promise初体验
看一下Promise在异步任务当中是如何使用的。需求:点一下“点击抽奖",在两秒后,页面弹窗展示是否中奖,中奖概率30%。
html文件代码如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基本使用</title>
<link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h2 class="page-header">Promise 初体验</h2>
<button class="btn btn-primary" id="btn">点击抽奖</button>
</div>
<script>
//生成随机数
function rand(m,n){
return Math.ceil(Math.random() * (n-m+1)) + m-1;
}
/**
点击按钮, 1s 后显示是否中奖(30%概率中奖)
若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券
若未中奖弹出 再接再厉
*/
//获取元素对象
const btn = document.querySelector('#btn');
//绑定单击事件
btn.addEventListener('click', function(){
//定时器
// setTimeout(() => {
// //30% 1-100 1 2 30
// //获取从1 - 100的一个随机数
// let n = rand(1, 100);
// //判断
// if(n <= 30){
// alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券');
// }else{
// alert('再接再厉');
// }
// }, 1000);
//Promise 形式实现 Promise是个构造函数,所以可以进行对象的实例化,其在实例化时需要接收一个参数,
//这个参数是个函数类型的值。
// resolve 解决 函数类型的数据 当异步任务成功时调用
// reject 拒绝 函数类型的数据 当异步任务失败时调用
const p = new Promise((resolve, reject) => {
setTimeout(() => {//由于Promise可以包裹异步操作,所以把setTimeout()函数包裹进来
//30% 1-100 1 2 30
//获取从1 - 100的一个随机数
let n = rand(1, 100);
//判断
if(n <= 30){
resolve(n); // 将 promise 对象的状态设置为 『成功』成功时调用resolve()
}else{
reject(n); // 将 promise 对象的状态设置为 『失败』 失败时调用reject()
}
}, 1000);
});
console.log(p);
//调用 then 方法 来进行值得输出,它要接收两个函数作为参数。第一个是成功时的回调函数,第二个是失败时的回调函数
// value 值
// reason 理由
p.then((value) => {
alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value);
}, (reason) => {
alert('再接再厉, 您的号码为 ' + reason);
});
});
</script>
</body>
</html>
3.Promise实践练习-fs读取文件
需求:从txt文件中读取一些汉字。
js文件代码如下:
const fs = require('fs');
//回调函数 形式
// fs.readFile('./resource/content.txt', (err, data) => {
// // 如果出错 则抛出错误
// if(err) throw err;
// //输出文件内容
// console.log(data.toString());
// });
//Promise 形式
let p = new Promise((resolve , reject) => {
fs.readFile('./resource/content.txt', (err, data) => {//Promise包裹fs这个异步操作。./resource/content.txt随便存些汉字
//如果出错
if(err) reject(err);
//如果成功
resolve(data);
});
});
//调用 then
p.then(value=>{//形参只有一个,箭头函数这里简写,直接加"=>"
console.log(value.toString());
}, reason=>{
console.log(reason);
});
下图为运行结果:
4.Promise实践练习-AJAX请求段子接口
html文件代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise 封装 AJAX</title>
<link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h2 class="page-header">Promise 封装 AJAX 操作</h2>
<button class="btn btn-primary" id="btn">点击发送 AJAX</button>
</div>
<script>
//接口地址 https://api.apiopen.top/getJoke
//获取元素对象
const btn = document.querySelector('#btn');
btn.addEventListener('click', function(){
//创建 Promise
const p = new Promise((resolve, reject) => {
//1.创建对象
const xhr = new XMLHttpRequest();
//2. 初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke');
//3. 发送
xhr.send();
//4. 处理响应结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断响应状态码 2xx
if(xhr.status >= 200 && xhr.status < 300){
//控制台输出响应体
resolve(xhr.response);
}else{
//控制台输出响应状态码
reject(xhr.status);
}
}
}
});
//调用then方法
p.then(value=>{
console.log("success " + value);
}, reason=>{
console.warn("fail " + reason);
});
});
</script>
</body>
</html>
5.Promise封装fs读取文件操作
需求:
封装一个函数 mineReadFile 读取文件内容
参数: path 文件路径
返回: promise 对象
js文件代码如下:
function mineReadFile(path){
return new Promise((resolve, reject) => {
//读取文件
require('fs').readFile(path, (err, data) =>{
//判断
if(err) reject(err);
//成功
resolve(data);
});
});
}
mineReadFile('./resource/content.txt')//因为返回的是Promise对象,所以直接加".then"。
.then(value=>{
//输出文件内容
console.log(value.toString());
}, reason=>{
console.log(reason);
});
下图为效果:
6.util.promisify方法进行promise风格转化
util.promisify官方描述
js文件代码如下:
//引入 util 模块
const util = require('util');
//引入 fs 模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('./resource/content.txt').then(value=>{
console.log(value.toString());
});
下图是效果:
7.Promise封装AJAX请求
html文件代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise封装AJAX操作</title>
</head>
<body>
<script>
/**
* 封装一个函数 sendAJAX 发送 GET AJAX 请求
* 参数 URL
* 返回结果 Promise 对象
*/
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的结果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
sendAJAX('https://api.apiopen.top/getJok')
.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
</script>
</body>
</html>
8.Promise对象状态属性介绍
Promise 的状态实例对象中的一个内置属性:[Promisestate」取值有以下三种情况:
pending 未决定的
resolved/fullfilled 成功
rejected 失败
我们在控制台输出一个promise对象可得到如下结果:
9.Promise对象结果属性介绍
实例Promise对象中的另一个属性 [PromiseResult」
保存着异步任务「成功/失败」的结果
仅能通过resolve或者reject赋值
10.Promise工作流程
Promise对象拥有两个实例方法then()和catch()。
then()方法
成功和失败的回调函数我们是通过then()添加,在promise状态改变时分别调用,也就是说没有执行改变状态的代码,then()方法不会被调用。promise构造函数中通常都是异步的,所以then方法往往都先于resolve和reject方法执行。所以promise内部需要有一个存储fulfill时调用函数的数组和一个存储reject时调用函数的数组。
then方法可以接收两个参数,且通常都是函数。第一个参数会添加到fulfill时调用的数组中,第二个参数添加到reject时调用的数组中。
当promise状态fulfill时,会把resolve(value)中的value值传给调用的函数中。
同理,当promise状态reject时,会把reject(reason)中的reason值传给调用的函数。