相关文章
- 阮一峰ES6: http://es6.ruanyifeng.com/#docs/promise
- 一次性让你懂async/await,解决回调地狱 - https://juejin.im/post/5b1ffff96fb9a01e345ba704
- ES6 系列之我们来聊聊 Promise- https://github.com/mqyqingfeng/Blog/issues/98
- Promise 必知必会(十道题) https://juejin.im/post/5a04066351882517c416715d#heading-4
回调函数
定义
- 一个函数的执行总是依赖另一个函数的执行结果,函数层层嵌套
举例
- 回调函数与同步、异步没有关系,回调函数只是一种实现方式,既可以有同步回调,也可以有异步回调,也可以有延迟函数回调
// click事件的回调函数
$('div').click(()=>{
alert(1)
})
// ajax的回调函数
$.get('/del.html',function(data){
$('.new').html(data);
})
异步函数
1. 什么是异步函数?
官方
- 无需等待被调用函数的返回值就继续向下执行的方法
我理解的
- 异步函数就是将一个函数分成AB两段来做,为避免造成网络堵塞,就把该任务,放在执行栈里面,先执行完同层级的同步函数,再去执行栈拿异步任务出来执行。
2. 异步函数有哪些?
- 定时器,事件和 ajax
3. js中事件的执行顺序问题
任务分类
- 同步任务
- 异步任务
精细划分 =>
- 宏任务:IO/setTimeout/serInterval
- 微任务:promise.then(catch/finally)
- (注:promise是声明立即执行)
执行顺序
- 依次从上向下执行
- 遇到同步语句 >> 立即执行
- 遇到宏任务 >> 放在宏任务队列
- 遇到微任务 >> 放在微任务队列
- 先返回执行同级的微任务
- 遇到同步语句 >> 立即执行
- 遇到宏任务 >> 放在宏任务队列
- 遇到微任务 >> 放在微任务队列
- (同级的微任务队列中没有微任务)再返回执行宏任务
- 拿出一个宏任务,按照上面的步骤执行即可
!宏任务的队列是全局的
!同级的微任务队列没有微任务时再去查找宏任务队列
- 拿出一个宏任务,按照上面的步骤执行即可
举例
/*
* 下面的代码b并不会等事件a完全执行完毕在执行
* 在延迟函数被触发的过程中就执行了函数b
* 当js引擎的event 队列空闲时才会去执行队列里等待的setTimeout的回调函数
调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间
*/
function a() {
setTimeout(() => {
console.log('执行a函数的延迟函数')
}, 3000)
console.log('执行a函数')
}
function b() {
console.log('执行b函数')
}
a();
b();
// 执行a函数
// VM640:8 执行b函数
// undefined
// VM640:3 执行a函数的延迟函数
4. 回调函数是什么?
定义
- 一个函数的执行总是依赖另一个函数的执行结果
举例
// click事件的回调函数
$('div').click(()=>{
alert(1)
})
// ajax的回调函数
$.get('/del.html',function(data){
$('.new').html(data);
})
Promise
promise解决的什么问题?
- 回调地狱:下一个事件的执行总是依赖于他的前一个事件的执行结果,嵌套层数过多,难以维护
- Promise使代码扁平化
- 回调举例
doSomething(){
...
doSomething2(){
...
}
}
!promise特点
1. promise构造函数立即执行,then是异步执行
const d = new Promise((resolve,reject)=>{
console.log(1)
resolve()
})
d.then(()=>{
console.log(3)
})
console.log(2)
2. promise.then()返回一个新的promise,因此可以实行then链式调用
const promise1 = new Promise((resolve,reject)=>{
console.log(1)
reject()
})
const promise2 = promise1.then(()=>{
console.log(2)
},()=>{
console.log(3)
})
console.log(promise1)
// 打印结果
// Promise {<resolved>: undefined}
// __proto__: Promise
// [[PromiseStatus]]: "rejected"
// [[PromiseValue]]: undefined
console.log(promise2)
// 打印结果
// Promise {<resolved>: undefined}
// __proto__: Promise
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: undefined
3. promise的状态只有一次改变机会,一旦改变就不会再变
const promise = new Promise((resolve,reject)=>{
resolve('success1')
reject('error')
resolve('success2')
})
promise.then((res)=>{
console.log('成功',res)
},(err)=>{
console.log('失败',err)
})
// '成功' success1
4. Promise只能执行一次,但是then/catch都可以多次调用,且每次调用都能立即拿到promise内部返回值
const promise1 = new Promise((resolve,reject)=>{
resolve('success')
})
const promise2 = new Promise((resolve,reject)=>{
reject('error')
})
promise1.then((res)=>{
console.log(res)
} // success
promise1.then((res)=>{
console.log(res)
} // success
promise1.catch((res)=>{
console.log(res)
} // error
5. .then .catch种return一个 Error对象,并不会抛出错误,所以并不会被后续的.catch捕获
- 因为返回任意一个非 promise 的值都会被包裹成 promise 对象
const promise1 = new Promise((resolve,reject)=>{
resolve('success')
})
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
// 打印结果
// then: Error: error!!!
引出async/await的作用
- generator函数的语法糖,是模拟generator的*和yield
- 解决promise层层then ,使得异步代码写的看起来更像同步事件
- 使用举例: 等待读取文件完成后,再执行console.log打印结果
// 读取文件
const readFile = (originUrl) => {
return new Promise((resolve, reject) => {
fs.readFile(originUrl, 'utf-8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data)
}
});
})
}
const generateCodeFun = async () => {
const originCode = await readFile('./test.js');
console.log(originCode)
}
async特点
- 使用了async的函数为异步函数
- async的函数内部自动返回一个状态为resolve的Promise对象:
- 若内部手动return,则返回一个状态为resolve,参数为return值的Promise;
- 若无return,返回的是一个状态为resolve,参数为return值的Promise;
- async内部所有await都执行完了才返回成功还是失败
// 无return
const a = async ()=>{
console.log(1)
}
a()
// 返回值:Promise {<resolved>: undefined}
const b = async ()=>{
console.log(1)
return 1
}
b()
// 返回值:Promise {<resolved>: 1}
- async返回reject的情况
- 内部使用未声明的变量或者函数
- 函数方法使用错误(如,对object使用push)
- 内部手动抛出一个错误
throw new Error
或者 - return Promise.reject('执行失败')
/* 1.使用未声明的变量 */
const a = async ()=>{
console.log(b);
}
a()
// 返回值: Promise {<rejected>: ReferenceError: b is not defined
/* 2.方法使用错误 */
const b = async ()=>{
const c = {name:1}
c.push(1)
}
b()
// 返回值:Promise {<rejected>: TypeError: c.push is not a function
/* 3.手动抛出一个错误 */
const c = async ()=>{
throw new Error;
}
c()
// 返回值:Promise {<rejected>: Error
/* 4.手动return Promise.reject() */
const e = async ()=>{
return Promise.reject(1);
}
e()
// 返回值:Promise {<rejected>: 1}
async函数内部一定要return
- 若手动调用reject,一定要return,不然就认为是resolve,且参数为undefined
// 正确的reject方法。必须将reject状态return出去。
async function PromiseError() {
return Promise.reject('有错误');
}
PromiseError()
.then(success => console.log('成功', success))
.catch(error => console.log('失败', error));
// 打印结果:失败 有错误
/*
* 错误的reject方法。必须将reject状态return出去
*/
async function PromiseError() {
Promise.reject('有错误');
}
PromiseError()
.then(success => console.log('成功', success))
.catch(error => console.log('失败', error));
// 打印结果:成功 undefined
await 特点
- await会暂停当前async function的执行,等待其指定的Promsie处理完成
- 若promise 成功执行(fulfilled),其回调的resolve()函数的参数作为await表达式的值,继续向下执行async function;
- 若Promise 执行异常(rejected),await 表达式会抛出 Promise 的异常原因;
- 若await 等待的表达式的值不是一个Promise,则返回值本身
- 举例:执行过程:
- await会等待timeout里面的Promise执行,也就是3秒后将setTimeout内容加入队列
- 3秒后打印出了1
- 接着执行console.log(2)
timeout() {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1)
resolve()
}, 3000);
});
}
test = async () => {
await this.timeout();
console.log(2)
}
this.test()
// 3秒后打印
// 1
// 2
async函数内部的map内使用await报错await is a reserved word解决方法
- 解决方法
const generateCodeFun = async (flagArr) => {
+ flagArr.map(async (item) => {
...
return item
})
}
async/await滥用问题
- js是单线程执行的,也就是一次只能执行一个,所有的异步都是使用同步模拟出来的,这么做的目的就是为了避免由于网络原因造成堵塞,如果一张图片没有请求到,下面不需要网络的函数们也不能加载执行只能干等着,性能非常不好
- 而await的提出又是为了解决同步代码需要他之前的异步代码的结果提出的
- 所以只在需要之前的异步函数的情况下使用await,不然岂不是辜负了设计者的良苦用心
async/await 应用
并行处理多个异步结果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2()
]);
console.log(result1, result2);
}