【Promise】入门-同步回调-异步回调-JS中的异常error处理-Promis的理解和使用-基本使用-链式调用-七个关键问题
预备知识
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. 同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
数组遍历相关的回调 / Promise的executor函数
const arr = [1, 3, 5];
arr.forEach(item => { // 遍历回调,同步回调,不会放入队列,一上来就要执行
console.log(item);
})
console.log('forEach()之后')

2. 异步回调
不会立即执行,会放入回调队列中将来执行
定时器回调 / 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. 错误的类型
Error
:所有错误的父类型
ReferenceError
:引用的变量不存在
console.log(a) // ReferenceError:a is not defined
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
RangeError
:数据值不在其所允许的范围内
function fn() {
fn()
}
fn()
// RangeError:Maximum call stack size exceeded
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)
}
- 错误对象
-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
// 出错之后
因为错误被捕获处理了,后面的代码才能运行下去,打印出‘出错之后’
一.什么是Promise:
Promise 是在 js 中进行异步编程的新解决方案。(以前旧的方案是单纯使用回调函数)
从语法来说,promise是一个构造函数。
从功能来说,promise对象用来封装一个异步操作,并且可以获得成功或失败的返回值。
JS中的常见的异步操作:定时器,AJAX中一般也是异步操作(也可以同步),回调函数可以理解为异步(不是严谨的异步操作)…等。
剩下的都是同步处理
二.为啥使用Promise:
promise使用回调函数更灵活。旧的回调函数必须在启动异步任务前指定。
promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至能在异步任务结束后指定多个)
promise支持链式调用,可以解决回调地狱问题。(回调地狱就是多层回调函数嵌套使用,就是套娃,这样就不利于阅读和异常处理。)
三. promise初体验:
效果:点击一个按钮,有30%概率显示中奖。
实现: 点击按钮后得到一个1到100间的随机数,小于等于30输出中奖,否则输出没中。期间用定时器模拟异步操作,在定时器里执行判断。
(1)基础的写法:
<button id="btn">click</button>
<script>
var btn = document.querySelector("#btn");
// 该函数返回一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
// 点击事件
btn.addEventListener('click',function(){
// 1秒的定时器,模拟异步操作
setTimeout(() => {
// 得到1到100间的一个数
let n = getRandomIntInclusive(1, 100);
if (n <= 30) {
alert('中奖喽');
} else {
alert('没中奖');
}
}, 1000);
})
</script>

<script>
var btn = document.querySelector("#btn");
// 得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
// 点击事件
btn.addEventListener('click',function(){
// 下面是promise ,resolve表示解决,reject表示拒绝 ,都是函数类型数据。
const p = new Promise((resolve,reject) => {
setTimeout(()=>{
let n = getRandomIntInclusive(1, 100);
if(n<=30){
resolve(); //将promise状态设为 “成功”
}else{
reject(); //将promise状态设为 “失败”
}
},1000)
});
// 调用then方法
p.then(()=>{
// “成功”执行这步
alert('中奖喽');
},()=>{
// “失败”执行这步
alert('没中奖');
})
})
假如要在上面输出结果后加上选中的号码:
因为.then()里并不能直接获得 n,所以在resolve与reject里把n当结果值返回。
// 点击事件
btn.addEventListener('click',function(){
// 下面是promise ,resolve表示解决,reject表示拒绝 ,都是函数类型数据。
const p = new Promise((resolve,reject) => {
setTimeout(()=>{
let n = getRandomIntInclusive(1, 100);
if(n<=30){
resolve(n); //将promise状态设为 “成功”,并把结果值n返回
}else{
reject(n); //将promise状态设为 “失败”,并把结果值n返回
}
},1000)
});
// 调用then方法
// value为值的意思,reason为理由的意思,都是形参
p.then((value)=>{
// “成功”执行
alert('中奖喽'+value);
},(reason)=>{
// “失败”执行
alert('没中奖'+reason);
})
})

四:promise体验ajax请求:
效果:点击按钮获得一句名言在控制台输出。
基础写法:
<script>
var btn = document.querySelector("#btn");
// 点击事件
btn.addEventListener('click',function(){
//创建对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open('GET',"http://poetry.apiopen.top/sentences");
//发送
xhr.send();
//处理响应结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status < 300){
//输出响应体
console.log(xhr.response);
}else{
//输出响应状态码
console.log(xhr.status);
}
}
}
})
</script>

// 点击事件
btn.addEventListener('click',function(){
const p = new Promise((resolve,reject) => {
//创建对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open('GET', "http://poetry.apiopen.top/sentences");
//发送
xhr.send();
//处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
//输出响应体
resolve(xhr.response);
} else {
//输出响应状态码
reject(xhr.status);
}
}
}
})
p.then(value=>{
console.log(value);
},reason=>{
//控制台输出警告信息
console.warn(reason);
})
})


五:Promise封装ajax请求:
跟上一步差不多。就是把其封装在一个sendAJAX()的自定义函数里。
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("http://poetry.apiopen.top/sentences")
.then(value=>{
console.log(value);
},reason=>{
//控制台输出警告信息
console.warn(reason);
})
六:promise的状态改变:
promise状态表示实例对象的一个属性
【PromiseState】。包括以下值:
(1)pending 未决定的
(2)resolved 或 fullfilled 成功
(3)rejected 失败
Promise对象的值表示实例对象的另一个属性
【PromiseResult】。保存着对象【成功/失败】的结果。而其状态改变只有以下两种可能:
(1)pending 变为resolved
(2)pending 变为 rejected
注:一个promise对象只能改变一次,无论成功或失败都会有一个结果数据,成功的称为 value , 失败的称为 reason 。
七:Promise基本流程图:

八:Promise的API 使用:
-
Promise 的构造函数:Promise(executor){}
(1)executor 函数:执行器 (resolve,reject)=> {}。
(2)resolve 函数:内部定义成功时调用函数 value => {} 。
(3)reject 函数:内部定义失败时调用函数 reason => {} 。
注意:Promise内部会立同步即调用executor,异步操作在执行器里执行。 -
Promise.prototype.then 方法: (onResolved, onRejected)=> {}
(1) onResolved 函数:成功的回调函数 (value) => {}
(2) onRejected 函数:失败的回调函数 (reason) => {}
注:指定用于得到成功value的成功回调和用于得到失败reason的失败回调是返回一个新的promise对象。 -
Promise.prototype.catch万法: (onRejected) => {}
onRejected.函数: 失败的回调函数(reason)=> {}
注:只是失败的调用。then()的语法糖,相当于: then(undefined, onRejected)。 -
Promise.resolve 方法: (value)=> {}
value: 成功的数据或promise对象
注:如果传入的参数为非Promise类 型的对象,则返回的结果为成功promise对象,如果传入的参数为Promise 对象,则参数的结果决定了resolve 的结果。 -
Promise.reject 方法: (reason) => {}
reason: 失败的原因
注:无论传入啥只返回一个失败的promise对象。 -
Promise.all 方法: (promises)=> {}
promises: 包含n个promise的数组
注:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败。失败了返回那个失败值。 -
Promise.race 方法: (promises)=> {}
promises: 包含n个promise的数组
注:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态。
来一个例子:
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('yes');
},1000)
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('come');
const result = Promise.race([p1,p2,p3]);
console.log(result);

九:使用Promise面临的关键问题:
1.如何改变 promise的状态?
(1) resolve(value): 如果当前是pending就会变为resolved。
(2) reject(reason): 如果当前是pending就会变为rejected。
(3)抛出异常 throw :如果当前是pending就会变为rejected。
let p1 = new Promise((resolve,reject)=>{
// resolve('success');
// reject('error');
// throw 'error';
})
2.一个 promise指定多个成功/失败回调函数,都会调用吗?
当promise改变为对应状态时都会调用。
let p = new Promise((resolve,reject)=>{
resolve('success');
})
// 第一次回调
p.then(value=>{
console.log("yes");
})
// 第二次回调
p.then(value=>{
console.log("oh yes");
})
3.改变 promiseT状态和指定回调函数谁先谁后?
(1)都有可能, 正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
(2)如何先改状态再指定回调?
①在执行 器中直接调用resolve(/reject();
②延迟更 长时间才调用then();
(3)什么时候才能得到数据?
①如果先指定的回调, 那当状态发生改变时,回调函数就会调用,得到数据
②如果先改变的状态, 那当指定回调时,回调函数就会调用,得到数据
4. promise.then()返回的新promise的结果状态由什么决定?
(1)简单表达: then()指定的回调函数执行的结果决定。
(2)详细表达:
*如果抛出异常, 新promise变为rejected, reaon为抛出的异常。
*如果返回的是非prormise的任意值,新promise变为resolved, value为返回的值。
*如果返回的是另一个新promise,此promise的结果就会成为新promise的结果。
let p = new Promise((resolve,reject) => {
// resolve('success');
// reject('No');
// throw 'oh no';
});
let result = p.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
console.log(result);
5. promise 如何串连多个操作任务?
(1) promise 的then()返回一个新的promise,可以开成then()的链式调用。
(2)通过then的链式调用串连多个同步/异步任务。
let p =new Promise((resolve,reject) => {
resolve("yes");
})
p.then(value => {
return new Promise((resolve,reject)=>{
resolve("oh yes~");
});
}).then(value => {
console.log(value);
})
输出结果:
oh yes~
let p =new Promise((resolve,reject) => {
resolve("yes");
})
p.then(value => {
return new Promise((resolve,reject)=>{
resolve("oh yes~");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
输出结果:
oh yes~
undefined
6. promise 的异常穿透。
(1)当使用promise的then链式调用时,可以在最后指定失败的回调。
(2)前面任何操作出 了异常,都会传到最后失败的回调中处理。
let p =new Promise((resolve,reject) => {
setTimeout(()=>{
resolve("yes");
},1000);
})
p.then(value => {
throw 'oh No';
}).then(value => {
console.log("123");
}).then(value => {
console.log("456");
}).catch(reason=>{
console.warn(reason);
})
输出结果:
oh No
7.中断 promise链。
(1)当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数。
(2)办法:在回调函数中返回一个pendding状态的promise对象。
未中断:
let p =new Promise((resolve,reject) => {
setTimeout(()=>{
resolve("yes");
},1000);
})
p.then(value => {
console.log("789");
}).then(value => {
console.log("123");
}).then(value => {
console.log("456");
}).catch(reason=>{
console.warn(reason);
})
输出结果:
789
123
456
中断:
let p =new Promise((resolve,reject) => {
setTimeout(()=>{
resolve("yes");
},1000);
})
p.then(value => {
console.log("789");
return new Promise(()=>{});
}).then(value => {
console.log("123");
}).then(value => {
console.log("456");
}).catch(reason=>{
console.warn(reason);
})
输出结果:
789
十:Promise的自定义封装:(略)
十一:async函数:
1.函数的返回值为promise对象。
2.promise对象的结果由async函数执行的返回值决定。
3.其实跟 then()方法返回结果是一样一样的。
1.返回一个非Promise对象,返回值是resolve。
async function main(){
return '123';
}
let res = main();
console.log(res);

async function main(){
return new Promise((resolve,reject)=>{
reject('NO');
});
}
let res = main();
console.log(res);

3.抛出异常也是失败
async function main(){
return new Promise((resolve,reject)=>{
reject('NO');
});
}
let res = main();
console.log(res);

十二.await表达式:
1.await右侧的表达式一般为promise对象,但也可以是其它的值。
2.如果表达式是promise对象,await返回的是promise成功的值。
3.如果表达式是其它值,直接将此值作为await的返回值。
注意:
1.await 必须写在async函数中,但async 函数中可以没有await 。
2.如果await的promise失败了,就会抛出异常,需要通过try…catch捕获处理。
1.右侧为promise对象
async function works(){
let p = new Promise((resolve,reject)=>{
resolve('oh yes')
})
let res = await p;
console.log(res);
}
works();
结果:
oh yes
2.右侧为其它值:
async function works(){
let p = new Promise((resolve,reject)=>{
resolve('oh yes')
})
// let res = await p;
let res = await 100;
console.log(res);
}
works();
结果:
100
3.如果promise是失败状态:
async function works(){
let p = new Promise((resolve,reject)=>{
// resolve('oh yes')
reject('err');
})
try{
let res = await p;
}catch(e){
console.log(e);
}
}
works();

十三.async与await结合发生ajax请求:
效果: 点击按钮获取一句名言。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">获取一句名言</button>
<script>
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);
}
}
}
});
}
var btn = document.querySelector("#btn");
btn.addEventListener('click',async function(){
let word = await sendAJAX("http://poetry.apiopen.top/sentences");
console.log(word);
})
</script>
</body>
</html>
