从零开始的Promise(二)
从零开始的Promise(三)
1. 预备知识
1.1 实例对象与函数对象
- 实例对象:new 函数产生的对象,称为实例对象,简称为对象
- 函数对象:将函数作为对象使用时,称为函数对象
function Fn() { // Fn只能称为函数
}
const fn = new Fn() // Fn只有new过的才可以称为构造函数
//fn称为实例对象
console.log(Fn.prototype)// Fn作为对象使用时,才可以称为函数对象
Fn.bind({}) //Fn作为函数对象使用
$('#test') // $作为函数使用
$.get('/test') // $作为函数对象使用
()左边是函数,点左边是对象(函数对象、实例对象)
1.2 两种类型的回调函数
1. 什么是回调函数
- 你定义的
- 你没有调
- 最终他执行了
2. 常见的回调函数与异步操作
- dom事件回调函数
- 定时器回调函数、
- ajax回调函数
- 生命周期回调函数
- 数据库操作
- fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})\
3. 同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
数组遍历相关的回调 / Promise的executor函数
const arr = [1, 3, 5];
arr.forEach(item => { // 遍历回调,同步回调,不会放入队列,一上来就要执行
console.log(item);
})
console.log('forEach()之后')
4. 异步回调
不会立即执行,会放入回调队列中将来执行
定时器回调 / ajax回调 / Promise成功或失败的回调
// 定时器回调
setTimeout(() => { // 异步回调,会放入队列中将来执行
console.log('timeout callback()')
}, 0)
console.log('setTimeout()之后')
// Promise 成功或失败的回调
new Promise((resolve, reject) => {
resolve(1)
}).then(
value => {console.log('value', value)},
reason => {console.log('reason', reason)}
)
console.log('----')
// ----
// value 1
js 引擎先把初始化的同步代码都执行完成后,才执行回调队列中的代码
1.3 JS中的异常error处理
1. 错误的类型
1. Error:所有错误的父类型
2. ReferenceError:引用的变量不存在
console.log(a) // ReferenceError:a is not defined
3. TypeError:数据类型不正确
let b
console.log(b.xxx)
// TypeError:Cannot read property 'xxx' of undefined
let c = {}
c.xxx()
// TypeError:c.xxx is not a function
4. RangeError:数据值不在其所允许的范围内
function fn() {
fn()
}
fn()
// RangeError:Maximum call stack size exceeded
5. SyntaxError:语法错误
const c = """"
// SyntaxError:Unexpected string
2. 错误处理(捕获与抛出)
抛出错误:throw error
function something() {
if (Date.now()%2===1) {
console.log('当前时间为奇数,可以执行任务')
} else { //如果时间为偶数抛出异常,由调用来处理
throw new Error('当前时间为偶数,无法执行任务')
}
}
捕获错误:try ... catch
// 捕获处理异常
try {
something()
} catch (error) {
alert(error.message)
}
3. 错误对象
- massage 属性:错误相关信息
- stack 属性:函数调用栈记录信息
try {
let d
console.log(d.xxx)
} catch (error) {
console.log(error.message)
console.log(error.stack)
}
console.log('出错之后')
// Cannot read property 'xxx' of undefined
// TypeError:Cannot read property 'xxx' of undefined
// 出错之后
因为错误被捕获处理了,后面的代码才能运行下去,打印出‘出错之后’
2. Promise的理解和使用
2.1 Promise是什么
1. 理解Promise
- 抽象表达:
- Promise 是一门新的技术(ES6 规范)
- Promise 是 JS 中进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数
- 具体表达:
- 从语法上看:Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法)
- 从功能上看:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
- 阮一峰的解释:
- 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
- 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
- Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
2. Promise 的状态的改变
实例对象promise中的一个属性 PromiseState
- pending 变为 resolved/fullfilled
- pending 变为 rejected 注意
- 对象的状态不受外界影响
- 只有这两种,且一个 promise 对象只能改变一次
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason。
3. Promise对象的值
实例对象promise的另一个值 PromiseResult,保存着对象 成功/失败 的值(value/reason)
- resolve/reject可以修改值
4. Promise 的基本流程
5. Promise 的基本使用
-
通过new创建一个Promise对象时, 接受一个函数(执行器函数--executor)作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
-
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,会执行promise对象的then方法传递过来的第一个参数(resolved回调函数), 并将异步操作的结果,作为参数value传递出去;
-
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,会执行promise对象的then方法传递过来的第二个参数(rejected回调函数),并将异步操作报出的错误,作为参数error/reason传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) { // onResolved
// success
}, function(reason) { // onRejected
// failure
});
then方法可以接受两个回调函数作为参数
- 第一个回调函数onResolved()是Promise对象的状态变为resolved时调用
- 第二个回调函数onRejected()是Promise对象的状态变为rejected时调用
- 这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数
.then()方法 和执行器(executor)都是同步执行的,.then()中的回调函数是异步执行的
1) 使用 1: 基本编码流程
// 创建 promise 对象(pending 状态), 指定执行器(executor)函数
const promise = new Promise((resolve, reject) => {
// 接受executor(执行器)函数作为new Promise()的参数, 这个执行器函数会被立即执行
console.log("立即执行");
setTimeout(() => {
const time = Date.now();
// 如果成功了, 调用 resolve(), 执行then传递过来的回调函数, 指定成功的 value值, 且promise变为 resolved 状态
if (time % 2 === 1) {
// promise的状态一旦确定下来, 就不会再更改, 即多次调用resolve/reject无效, 只执行第一个
resolve("成功的值 " + time);
} else {
// 如果失败了, 调用 reject(), 执行then传递过来的回调函数, 指定失败的 reason值, 且promise变为rejected 状态
reject("失败的值" + time);
}
}, 2000);
});
promise.then(
(value) => { // 传入的回调函数, 当Promise的状态为resolved/fullfilled的时候调用resolve方法, 执行这个回调
console.log("成功的回调:", value);
},
(reason) => { // 传入的回调函数, 当Promise的状态为rejected的时候调用reject方法, 执行这个回调
console.log("失败的回调:", reason);
}
);
console.log(".then方法是立即执行的, 但then方法内的回调是异步执行的");
// 立即执行
// .then方法是立即执行的, 但then方法内的回调是异步执行的
// 失败的回调: 失败的值1677679498530
2) 使用 2: 使用 promise 封装基于定时器的异步
function doDelay(time) {
// 1. 创建 promise 对象
return new Promise((resolve, reject) => {
// 2. 启动异步任务
console.log('启动异步任务')
setTimeout(() => {
console.log('延迟任务开始执行...')
const time = Date.now() // 假设: 时间为奇数代表成功, 为偶数代表失败
if (time % 2 === 1) { // 成功了
// 3. 1. 如果成功了, 调用 resolve()并传入成功的 value
resolve('成功的数据 ' + time)
} else { // 失败了
// 3.2. 如果失败了, 调用 reject()并传入失败的 reason
reject('失败的数据 ' + time)
}
}, time)
})
}
const promise = doDelay(2000)
promise.then(
value => { // 传入回调函数, 等待成功时调用
console.log('成功的 value: ', value)
},
reason => { // 传入回调函数, 等待失败时调用
console.log('失败的 reason: ', reason)
},
)
3) 使用 3: 使用 promise 封装 ajax 异步请求
- 可复用的发 ajax 请求的函数: xhr + promise
function promiseAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
const { status, response } = xhr
// 请求成功, 调用 resolve(value)
if (status >= 200 && status < 300) {
resolve(JSON.parse(response))
} else { // 请求失败, 调用 reject(reason)
reject(new Error('请求失败: status: ' + status))
}
}
xhr.open("GET", url)
xhr.send()
})
}
promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=video').then(
data => {
console.log('显示成功数据', data)
},
error => {
alert(error.message)
}
)
2.2 为什么要用 Promise?
1. 指定回调函数的方式更加灵活
- 旧的: 必须在启动异步任务前指定
- promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
2. 支持链式调用, 可以解决回调地狱问题
1. 什么是回调地狱?
回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件
2. 回调地狱的缺点?
不便于阅读 不便于异常处理
3. 解决方案?
promise 链式调用
4. 终极解决方案?
async/await
/*
1. 指定回调函数的方式更加灵活:
旧的: 必须在启动异步任务前指定
promise: 启动异步任务 => 返回 promie 对象 => 给 promise 对象绑定回调函数
(甚至可以在异步任务结束后指定)
2. 支持链式调用, 可以解决回调地狱问题
什么是回调地狱? 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函
数执行的条件
回调地狱的缺点? 不便于阅读 / 不便于异常处理
解决方案? promise 链式调用
终极解决方案? async/await
*/
// 成功的回调函数
function successCallback(result) {
console.log("声音文件创建成功: " + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("声音文件创建失败: " + error);
}
/* 1.1 使用纯回调函数 */
createAudioFileAsync(audioSettings, successCallback, failureCallback)
/* 1.2. 使用 Promise */
const promise = createAudioFileAsync(audioSettings); // 2
setTimeout(() => {
promise.then(successCallback, failureCallback);
}, 3000);
/*
2.1. 回调地狱
*/
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log('Got the final result: ' + finalResult)
}, failureCallback)
}, failureCallback)
}, failureCallback)
/*
2.2. 使用 promise 的链式调用解决回调地狱
*/
doSomething().then(function (result) {
return doSomethingElse(result)
})
.then(function (newResult) {
return doThirdThing(newResult)
})
.then(function (finalResult) {
console.log('Got the final result: ' + finalResult)
})
.catch(failureCallback)
/*
2.3. async/await: 回调地狱的终极解决方案
*/
async function request() {
try {
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log('Got the final result: ' + finalResult)
} catch (error) {
failureCallback(error)
}
}
3. 代码实例
Promise实现定时器-抽奖
<!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 - 100的一个随机数
// let n = rand(1, 100);
// //判断
// if(n <= 30){
// alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券');
// }else{
// alert('再接再厉');
// }
// }, 1000);
//Promise 形式实现
// resolve 解决 函数类型的数据
// reject 拒绝 函数类型的数据
const p = new Promise((resolve, reject) => {
setTimeout(() => {
//30% 1-100 1 2 30
//获取从1 - 100的一个随机数
let n = rand(1, 100);
//判断
if (n <= 30) {
resolve(n); // 将 promise 对象的状态设置为 『成功』
} else {
reject(n); // 将 promise 对象的状态设置为 『失败』
}
}, 1000);
});
console.log(p);
//调用 then 方法
// value 值
// reason 理由
// 上面两个值随便写也行,但最好不要乱写
p.then((value) => {
// 对象状态为成功时的回调
alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value);
}, (reason) => {
// 对象状态为失败时的回调
alert('再接再厉, 您的号码为 ' + reason);
});
});
</script>
</body>
</html>
Promise实现AJAX
<!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) {
//控制台输出响应体
// 未封装 console.log(xhr.response);
resolve(xhr.response);
} else {
//控制台输出响应状态码
// 未封装 console.log(xhr.status);
reject(xhr.status);
}
}
}
});
//调用then方法
p.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
});
</script>
</body>
</html>
使用 promise 封装 ajax 异步请求
<!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/getJoke')
.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
</script>
</body>
</html>