一、基础知识准备
1、函数对象与实例对象
函数对象:将函数作为对象使用时,简称为函数对象
实例对象:new函数产生的对象,简称为对象
function Fn(){ //Fn函数
}
const fn = new Fn() //Fn是构造函数 fn是实例对象(对象)
console.log(Fn.prototype) //Fn是函数对象
//记忆:Fn放在,括号左边是函数,点的左边是对象
Fn.bind({}) //只有函数对象才有 call()、apply()、bind()方法
//解释:这里所有函数都是Fn的实例,实例会去找原型对象上的方法
Fn.call({}) //Fn是函数对象
$('#test') //jQuery函数
$.get('/test') //jQuery函数对象
2、两种类型的回调函数
首先我们需要清楚这几点:
- 回调函数:函数可以作为参数在另一个函数中调用
- 同步回调函数
const arr = [1, 3, 5]
arr.forEach(item => { //遍历回调:每取出一个元素,调用一次 同步回调函数
console.log(item) //不会放入队列 一上来就执行
})
console.log('forEach()之后')
//1
//3
//5
//forEach()之后
- 异步回调函数
setTimeout(() => { //异步回调函数 会放入队列中将来执行
console.log('后执行')
},0)
console.log('先执行')
//先执行
//后执行
(1)同步回调
理解:立即执行,完全执行完了才会结束,不会放入回调队列中
例子:数组遍历相关的回调函数、Promise的excutor函数
(2)异步回调
理解:不会立即执行,会放入回调队列中将来执行
例子:定时器回调、ajax回调、Promise的成功/失败回调
判断回调是同步还是异步:打印输出一下
3、JS的error处理
参考官方文档:JS-error
(1)错误类型
- Error:所有错误的父类型
常见内置错误:
- ReferenceError:引用的变量不存在
console.log(a) //ReferenceError: a is not defined
console.log('......') //没有捕获错误,下面的代码不会执行
- TypeError:数据类型不正确的错误
let b
console.log(b.xxx) //TypeError: Cannot read property 'xxx' of undefined
let b
b.xxx() //TypeError: Cannot read property 'xxx' of undefined
let b = {}
b.xxx() //TypeError: b.xxx is not a function
- RangeError:数据值不在其所允许的范围
function fn() {
fn()
}
fn()
- SyntaxError:语法错误
const c ="""" //SyntaxError: Unexpected string
(2)错误处理
- 捕获错误:try...catch
try {
let d
console.log(d.xxx)
} catch (error) {
console.log(error) //error中包含message和stack属性
}
try {
let d
console.log(d.xxx)
} catch (error) {
console.log(error.message)
console.log(error.stack)
}
console.log('出错之后')
- 抛出错误:throw error
function something() {
if(Date.now()%2===1) {
console.log('当前时间为奇数,可以执行任务')
} else { //如果时间为偶数,抛出异常,由调用者来处理
throw new Error('当前时间为偶数,无法执行任务')
}
}
//捕获处理异常
try {
something()
} catch (error) {
alert(error.message) //调用者决定如何处理异常
}
(3)错误对象
- message属性:错误相关信息
- stack属性:函数调用栈记录信息
二、Promise的理解和使用
1、Promise是什么?
(1)理解
抽象表达:Promise是JS中进行异步编程的解决方案(旧方案--纯回调)
具体表达:
- 语法上来说,Promise是一个构造函数
- 功能上来说,Promise对象用来封装一个异步操作,并可以获取其结果
(2)Promise的状态改变
Promise有三种状态:pending(进行中,未确定)、resolved(已成功)、rejected(已失败)
状态改变:
- pending变为resolved
- pending变为rejected
注意:
- 只有两种状态改变,且一个Promise对象只能改变一次
- 无论成功失败,都会有一个结果数据,成功的结果数据为value,失败为reason
(3)Promise的基本流程
(4)Promise的基本使用
// 1 创建一个新的promise对象
const p = new Promise((resolve, reject) => { //执行器函数excutor 同步回调
// 2 异步操作任务
setTimeout(() => {
const time = Date.now() //偶数成功,否则失败
// 3.1 成功 调用resolve(value)
if(time%2 == 0) {
resolve('成功的数据,time=' + time)
} else {
// 3.2 失败 调用reject(reason)
reject('失败的数据,time=' + time)
}
},1000)
})
p.then(
value => { //接收得到成功的value数据 onResolved
console.log('成功的回调',value)
},
reason => { //接收得到失败的reason数据 onRejected
console.log('失败的回调',reason)
}
)
2、为什么要用Promise?
(1)指定回调函数的方式更加灵活
旧方案:必须在启动异步任务前指定
Promise:启动异步任务=>返回Promise对象=>给Promise对象绑定回调函数(可以在异步任务结束后指定/多个)
(2)支持链式调用,解决回调地狱问题
1、什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。
----回调函数地狱涉及多个串联执行的异步操作,下一个异步任务执行是以上一个异步任务的结果为条件的
2、回调地狱的特点?
不便于阅读,不便于异常处理
3、解决方案?
Promise链式调用(无回调函数嵌套问题)
----代码从上往下写,非常易于阅读。异步任务从上往下执行,异常统一最后处理,非常方便。不过会有异常传透问题(错误传递):前面的异常会一直往下传递,最后才处理
但是还有回调函数没有解决(程序员开发角度看)
4、终极解决方案?
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?
(1)API(语法/前后台交互的接口)
1. Promise构造函数:Promise(excutor) {}
- excutor函数:执行器 (resolve, reject) =>{}
- resolve函数:内部定义成功时调用 value =>{}
- reject函数:内部定义失败时调用 reason =>{}
说明:excutor会在Promise内部立即同步回调,异步操作在执行器中执行
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功的数据')
//reject('失败的数据') 只会执行第一个,因为promise状态只会改变一次,且不能变
}
},1000)
}).then(
value => {
console.log('onResolved()1',value)
}
).catch(
reason => {
console.log('onRejected()1',reason)
}
)
2. Promise.prototype.then方法:(onResolved, onRejected) => {}
- onResolved函数:成功的回调函数 (value) => {}
- onRejected函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调 返回一个新的promise对象
3. Promise.prototype.catch方法:(onRejected) => {}
- onRejected函数:失败的回调函数 (reason) => {}
说明:then()的语法糖,相当于:then(undefined, onRejected)
4. Promise.resolve方法:(value) => {}
- value:成功的数据或promise对象
说明:返回一个成功/失败的promise对象
//产生一个成功值为1的promise对象
//方法一
const p1 = new Promise((resolve,reject) => {
resolve(1)
})
//方法二
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
p1.then(value => {console.log(value)})
p2.then(value => {console.log(value)})
p3.catch(reason => {console.log(reason)})
5. Promise.reject方法:(reason) => {}
- reason:失败的原因
说明:返回一个失败的promise对象
6. Promise.all方法:(promises) => {}
- promises:包含n个promise的数组
说明:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败
const pAll = Promise.all([p1, p2, p3])
pAll.then(
value => {
console.log('all onResolved()', value)
},
reason => {
console.log('all onRejected()', reason)
}
)
// all onRejected() 3
//将p3去掉
const pAll = Promise.all([p1, p2])
pAll.then(
values => {
console.log('all onResolved()', values)
},
reason => {
console.log('all onRejected()', reason)
}
)
// all onResolved() [1, 2]
//注意:这里打印出的数组元素位置跟all([p1, p2])是一一对应的
7. Promise.race方法:(promises) => {}
- promises:包含n个promise的数组
说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
//这里修改下p1,给它设置个延时,这样第一个完成的promise是p2
const p1 = new Promise(() => {
setTimeout(() => {
resolve(1)
},1000)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
const pRace = Promise.race([p1, p2, p3])
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
// race onResolved() 2
(2)Promise的几个关键问题
1. 如何改变Promise的状态?
- resolve(value):如果当前是pending,就会变成resolved
- reject(reason):如果当前是pending,就会变成rejected
- 抛出异常:如果当前是pending,就会变成rejected
const p = new Promise((resolve, reject) => {
//resolve(1) //promise变为resolved状态
//reject(2) //promise变为rejected状态
//throw new Error('出错了') //抛出异常 promise变为rejected状态 reason为抛出的error
throw 3 //抛出异常 promise变为rejected状态 reason为抛出的 3
})
p.then(
value => {}
reason => {console.log('reason',reason)} //reason 3
)
2. 一个Promise指定多个成功/失败回调函数,都会调用吗?
当Promise改为对应状态时都会调用
const p = new Promise((resolve, reject) => {
throw 3
})
p.then(
value => {}
reason => {console.log('reason1',reason)}
)
p.then(
value => {}
reason => {console.log('reason2',reason)}
)
//reason1 3
//reason2 3
3. 改变Promise状态和指定回调函数,谁先谁后?
都有可能,正常情况下先指定回调再改变状态,但也可以先改变状态再指定回调
//常规:先指定回调,后改变状态
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) //后改变的状态(同时指定数据),异步执行回调函数
},1000);
}).then( //先指定回调函数,并保存当前指定的回调函数
value => {}
reason => {console.log('reason',reason)}
)
如何先改状态再指定回调?
- 在执行器中直接调用resolve()/reject()
new Promise((resolve, reject) => {
resolve(1) //先改变的状态(同时指定数据)
}).then( //后指定回调函数,异步执行回调函数
value => {console.log('value',value)}
reason => {console.log('reason',reason)}
)
console.log('------')
//注意:resolve和then都是同步执行 value/reason异步执行
//打印结果:
//------
//value 1
- 延迟更长时间才调用then()
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
},1000);
})
setTimeout(() => {
p.then(
value => {console.log('value',value)}
reason => {console.log('reason',reason)}
)
},2000);
什么时候才能得到数据?
- 如果先指定回调,当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变状态,当指定回调时,回调函数就会调用,得到数据
4. promise.then()返回的新promise的结果状态由什么决定?(重点)
简单表达:由then()指定的回调函数执行的结果决定
详细表达:
- 如果抛出异常,新promise变为rejected,reason为抛出的异常
- 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值
- 如果返回的是另一个新promise,这个promise的结果就会成为新promise的结果
//执行成功的情况
new Promise((resolve, reject) => {
resolve(1)
//reject(1)
}).then(
value => {
console.log('onResolved1()',value)
// 1. 这里如果没有写return返回值 则默认为 return undefined
// 2. return 2
// 3. return Promise.resolve(3)
// 4. return Promise.reject(4)
// 5. throw 5
}
reason => {console.log('onRejected1()',reason)}
).then( //新promise的状态由上一个执行的回调函数value/reason决定
value => {console.log('onResolved2()',value)}
reason => {console.log('onRejected2()',reason)}
)
//第一个then打印:onResolved1() 1
//第二个then打印:1. onResolved2() undefined
// 2. onResolved2() 2
// 3. onResolved2() 3
// 4. onRejected2() 4
// 5. onRejected2() 5
//执行失败的情况
new Promise((resolve, reject) => {
reject(1)
}).then(
value => {console.log('onResolved1()',value)}
reason => {
console.log('onRejected1()',reason)
// 1. 这里如果没有写return返回值 则默认为 return undefined
//其他返回结果情况同上
}
).then( //上一个then中执行的回调函数reason没有报错,且return undefined
//说明这里的then返回的promise成功,且成功的值为undefined
value => {console.log('onResolved2()',value)}
reason => {console.log('onRejected2()',reason)}
)
//onRejected1() 1
//onResolved2() undefined
//注意:第一个then执行失败的回调函数,后面的then不一定也执行失败的回调函数,而取决于上一个then执行的回调函数的结果
5. promise如何串联多个操作任务?
- promise的then()返回一个新的promise,可以看成then()的链式调用
- 通过then的链式调用串连多个同步/异步任务
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行任务1(异步)')
resolve(1)
},1000);
}).then(
value => {
console.log('任务1的结果:',value)
console.log('执行任务2(同步)')
return 2
}
).then(
value => {
console.log('任务2的结果:',value)
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行任务3(异步)')
resolve(3)
},1000);
})
}
).then(
value => {
console.log('任务3的结果:',value)
}
)
//打印结果:
//执行任务1(异步)
//任务1的结果:1
//执行任务2(同步)
//任务2的结果:2
//执行任务3(异步)
//任务3的结果:3
6. Promise异常传透?
- 当使用promise的链式调用时,可以在最后指定失败的回调
- 前面任何操作出了异常,都会传到最后失败的回调中处理
new Promise((resolve, reject) => {
reject(1)
}).then(
value => {
console.log('onResolved1()',value)
return 2
},
// reason => {throw reason}
).then(
value => {
console.log('onResolved2()',value)
return 3
},
// reason => {throw reason}
).then(
value => {
console.log('onResolved3()',value)
},
// reason => {throw reason}
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
//前面的then都失败,才会传递到下面执行catch
//失败状态: 抛出异常 reason => {throw reason} 默认情况
// 返回失败promise reason => Promise.reject(reason)
7. 中断promise链?
- 当使用promise的链式调用时,在中间中断,不再调用后面的回调函数
- 办法:在回调函数中返回一个pending状态的promise状态
new Promise((resolve, reject) => {
reject(1)
}).then(
value => {
console.log('onResolved1()',value)
return 2
},
// reason => {throw reason}
).then(
value => {
console.log('onResolved2()',value)
return 3
},
reason => Promise.reject(reason)
).catch(
reason => {
console.log('onRejected1()', reason) // onRejected1() 1
}
//1.若这里什么都不加 则下一个then会进入成功回调 打印 onResolved3() undefined
//2.若这里加上 throw reason 或 return Promise.reject(reason) 则下一个then进入失败的回调
// 下面会打印 onRejected2() 1
return new Promise(() => {}) //返回一个pending的promise(决定下一个then的状态) 中断promise链
).then(
value => {
console.log('onResolved3()',value)
},
reason => {
console.log('onRejected2()', reason)
}
)
//onRejected1() 1
三、手写Promise