一、准备
1.函数对象和实例对象
函数对象:将函数作为对象使用时, 简称为函数对象。
实例对象:new 函数产生的对象, 简称为对象。
2.回调函数
同步回调函数:立即执行,完全执行完了才会结束,不会放入回调队列中。例如 数组遍历相关的回调函数 / Promise的excutor函数。
异步回调函数:不会立即执行, 会放入回调队列中将来执行。例如 定时器回调 / ajax回调 / Promise的成功|失败的回调。
//1.同步回调函数
const arr = [1,3,5];
arr.forEach(item => {
//遍历回调
console.log(item);//同步回调函数
});
console.log('forEach()之后');//??谁先执行,上面的回调还是下面的输出
//2.异步回调函数
setTimeout(() => {
console.log("setTimeout-callback");//异步回调会放入队列中来执行
}, 0);
console.log('setTimeout-after');
3.JS中的错误(Error)和错误处理
JS中错误的类型:
- Error: 所有错误的父类型
- ReferenceError: 引用的变量不存在
- TypeError: 数据类型不正确的错误
- RangeError: 数据值不在其所允许的范围内
- SyntaxError: 语法错误
错误的处理:
- 捕获错误:try...catch
- 抛出错误: throw error
错误对象:一般有两个基本属性,分别是message(错误的相关信息),stack(函数调用栈记录信息)。
二、Promise的理解和使用
1.Promise是什么
抽象表达:Promise是JS中进行异步编程的新的解决方案。
具体表达:语法上来讲其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。功能上来说,promise对象用来封装一个异步操作并可以获取其结果。
特点:Promise的状态改变(只有2种, 只能改变一次)
- pending变为resolved
- pending变为rejected
Promise的基本流程:
基本语法如下:
//1.创建一个新的promise对象
const p = new Promise((resolve,reject) => {
//2.执行异步操作任务
setTimeout(() => {
const time = Date.now();
if(time%2 == 0){
resolve('成功的数据,time= '+time);
}
else{
reject('失败的数据,time= '+time);
}
}, 1000);
//3.1 如果执行成功了,调用resolve(value)
//3.2 如果执行失败了,调用reject(reason)
})
p.then(
value => {
//接受得到成功的value onResolved
console.log('成功的回调', value);
},
reason => {
//接受得到失败的reason onRejected
console.log('失败的的回调', reason);
}
)
2.为什么使用Promise
原因如下:
- 指定回调函数的方式更加灵活
旧方法:必须在启动异步任务前指定。promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定) - 支持链式调用, 可以解决回调地狱问题
什么是回调地狱---回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件。回调地狱的缺点---不便于阅读 / 不便于异常处理。解决方案---promise链式调用。终极解决方案---async/await(之后会说到,这和Promise本质上一样)。
3.具体如何使用Promise
主要API:
Promise构造函数: Promise (excutor) {}
Promise.prototype.then方法: (onResolved, onRejected) => {}
Promise.prototype.catch方法: (onRejected) => {}
Promise.resolve方法: (value) => {}
Promise.reject方法: (reason) => {}
Promise.all方法: (promises) => {}
Promise.race方法: (promises) => {}
代码如下:
/*
1. Promise构造函数: Promise (excutor) {}
excutor函数: 同步执行 (resolve, reject) => {}
resolve函数: 内部定义成功时我们调用的函数 value => {}
reject函数: 内部定义失败时我们调用的函数 reason => {}
说明: excutor会在Promise内部立即同步回调,异步操作在执行器中执行
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对象
5. Promise.reject方法: (reason) => {}
reason: 失败的原因
说明: 返回一个失败的promise对象
6. Promise.all方法: (promises) => {}
promises: 包含n个promise的数组
说明: 返回一个新的promise, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败
7. Promise.race方法: (promises) => {}
promises: 包含n个promise的数组
说明: 返回一个新的promise, 第一个完成的promise的结果状态就是最终的结果状态
*/
new Promise((resolve,reject) => {
setTimeout(() => {
resolve("成功!");
}, 1000);
}).then(
value => {
console.log("onResolve:"+value);
}
).catch(reason => {
console.log("onReject:"+reason);
});
//产生一个成功值为1的promise对象
const p1 = new Promise((resolve,reject) => {
resolve("p"+1);
})
const p2 = Promise.resolve("p"+2);
const p3 = Promise.reject("p"+3);
p1.then(value => {
console.log(value);
});
p2.then(value => {
console.log(value);
});
p3.catch(reason => {
console.log(reason);
});
const pAll = Promise.all([p1,p2]);//只有都成功了才去处理,一旦其中一个失败就整体失败
pAll.then(
values => {
console.log("all onResolve()--"+values);
},
reason => {
console.log("all onReject(),error--p"+reason);
}
);
const pRace =Promise.race([p1,p2,p3])//看第一个是否成功
pRace.then(
value => {
console.log(value);
},
reason => {
console.log(reason);
}
)
4.关于Promise的几个问题
如何改变Promise的状态?
- resolve(value): 如果当前是pendding就会变为resolved
- reject(reason): 如果当前是pendding就会变为rejected
- 抛出异常: 如果当前是pendding就会变为rejected(这也是一种方式)
一个promise指定多个成功/失败回调函数, 都会调用吗?
- 当promise改变为对应状态时都会调用
代码如下:
const p = new Promise((resolve,reject) => {
resolve(1);//promise变为resolved状态
reject(2);//promise变为rejected状态
throw new Error('出错了!');//promise也变为rejected状态
throw 3;//抛出异常,promise变为rejected状态,reason为 3
});
p.then(
value => {
console.log("1:"+value)
},
reason => {
console.log('reason1:'+reason);
});
p.then(
value => {
console.log("2:"+value)
},
reason => {
console.log('reason2:' + reason);
});
改变promise状态和指定回调函数谁先谁后?
- 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
那么,如何先改状态再指定回调?
- 在执行器中直接调用resolve()/reject()
- 延迟更长时间才调用then()
那什么时候才能得到数据?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
代码如下:
//1.第一种执行顺序(常规)
new Promise((resolve,reject) => {
setTimeout(() => {
resolve(1);//后改变状态(同时指定状态),异步执行之前保存的回调函数
reject(2);
}, 1000);
}).then(//先指定回调函数,保存当前指定回调函数
value =>{
console.log("成功"+value);
},
reason => {
console.log("失败"+reason);
}
)
//2.第二种执行顺序
new Promise((resolve, reject) => {
resolve(1);//先改状态(同时指定状态)
reject(2);
}).then(//后指定回调函数,并异步调用回调函数
value => {
console.log("成功" + value);
},
reason => {
console.log("失败" + reason);
}
)
promise.then()返回的新promise的结果状态由什么决定?
- 简单表达: 由then()指定的回调函数执行的结果决定
详细表达:
- 如果抛出异常, 新promise变为rejected, reason为抛出的异常.
- 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值(注意如果返回的是个未定义的变量什么的,也就是异常。也会变为rejected。reason为错误原因).
- 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果.
代码如下:
new Promise((resolve, reject) => {
resolve(1);
}).then(
value => {
console.log("成功1 " + value);
//return Promise.resolve(3);
//return Promise.reject(4);
return ;
},
reason => {
console.log("失败1 " + reason);
}
).then(
value => {
console.log("成功2 " + value);
},
reason => {
console.log("失败2 " + reason);
}
)
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) => {
// 启动任务3(异步)
setTimeout(() => {
console.log('执行任务3(异步))')
resolve(3)
}, 1000);
})
}
).then(
value => {
console.log('任务3的结果: ', value)
}
)
promise异常传透?
- 当使用promise的then链式调用时, 可以在最后指定失败的回调
- 前面任何操作出了异常, 都会传到最后失败的回调中处理
中断promise链?
- 当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数
- 办法: 在回调函数中返回一个pendding状态的promise对象
代码如下:
new Promise((resolve, reject) => {
// resolve(1)
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 => Promise.reject(reason)
).catch(reason => {
console.log('onReejected1()', reason)
// throw reason
// return Promise.reject(reason)
return new Promise(() => { }) // 返回一个pending的promise 中断promise链
}).then(
value => {
console.log('onResolved3()', value)
},
reason => {
console.log('onReejected2()', reason)
}
)
5.自定义Promise
详细代码见github
三、asyn/await的语法和使用
1. async 函数
- 函数的返回值为promise对象
- promise对象的结果由async函数执行的返回值决定
2. await 表达式
- await右侧的表达式一般为promise对象, 但也可以是其它的值
- 如果表达式是promise对象, await返回的是promise成功的值
如果表达式是其它值, 直接将此值(表达式本身 )作为await的返回值
await必须写在async函数中, 但async函数中可以没有await。如果await的promise失败了, 就会抛出异常, 需要通过try...catch来捕获处理
代码如下:
function fn2() {
return Promise.reject(2)
}
async function test() {
/* let result = fn2().then(value => {
console.log('value',value)
},
reason => {
console.log('reason',reason)
});*/
try {
let result = await fn2();
console.log('result', result)
} catch (error) {
console.log('error', error)
}
}
test()
四、宏队列与微队列
具体示意图:
理解
- 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM事件回调/ajax回调
- 微列队: 用来保存待执行的微任务(回调), 比如: promise的回调/MutationObserver的回调
- JS执行时会区别这2个队列。JS引擎首先必须先执行所有的初始化同步任务代码。每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行。
代码如下:
setTimeout(() => {
console.log('setTimeout1')
Promise.resolve(3).then(value => {
console.log('onResolved3', value)
})
})
setTimeout(() => {
console.log('setTimeout2')
})
Promise.resolve(1).then(value => {
console.log('onResolved1', value)
})
Promise.resolve(2).then(value => {
console.log('onResolved2', value)
})
/*
onResolved1 1
onResolved2 2
setTimeout1
onResolved3 3
setTimeout2*/
//node上执行结果(之后学习了node再来深入探讨)
/*
onResolved1 1
onResolved2 2
setTimeout1
setTimeout2
onResolved3 3*/
这里看出微队列的优先级更高。
下面探讨几个面试题
//题一
setTimeout(() => {
console.log(1)
}, 0);
Promise.resolve().then(() => {
console.log(2);
});
Promise.resolve().then(() => {
console.log(4);
});
console.log(3)
//浏览器输出顺序:3 2 4 1
//题二
setTimeout(() => {
console.log(1);
}, 0);
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
});
console.log(5);
//浏览器输出顺序:2 5 3 4 1
//题三
const first = () => (new Promise((resolve, reject) => {
console.log(3)
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(5)
resolve(6) //会被忽略,因为状态已经改变过了
}, 0)
resolve(1)
})
resolve(2)
p.then((arg) => {
console.log(arg)
})
}))
first().then((arg) => {
console.log(arg)
})
console.log(4)
//浏览器输出顺序:3 7 4 1 2 5
//题四
setTimeout(() => {
console.log("0")
}, 0)
new Promise((resolve, reject) => {
console.log("1")
resolve()
}).then(() => {
console.log("2")
new Promise((resolve, reject) => {
console.log("3")
resolve()
}).then(() => {
console.log("4")
}).then(() => {
console.log("5")
})
}).then(() => {
console.log("6")
})
new Promise((resolve, reject) => {
console.log("7")
resolve()
}).then(() => {
console.log("8")
})
//浏览器输出顺序:1 7 2 3 8 4 6 5 0
/*第四题详细解释
1.首先将‘0’放入宏队列等待微队列执行完毕后再执行,此时宏队列情况[0]
2.遇到第一个Promise,其中的执行器为同步执行所以直接输出‘1’
3.接下来执行resolve(),将‘2’放入微队列此时微队列情况[2],由于‘2’还没执行所以后续代码暂时不考虑
4.此时遇到第二个Promise,同步执行‘7’,直接输出后再执行resolve(),将‘8’放入微队列等待执行,此时的微队列情况[2,8]
5.接下来执行微队列代码,首先输出‘2’,然后遇到第三个Promise,直接输出‘3’,再执行resolve(),将‘4’放入微队列,后续代码需要等‘4’执行后才会考虑,但是该Promise已经执行完了有了结果,所以将之后的then放入微队列,即‘6’.此时微队列情况[8,4,6]
6.继续执行微队列,首先输出‘8’,然后输出‘4’,然后将‘4’后面的then送入微队列,所以此时微队列情况[6,5]
7.继续执行微队列,输出‘6’,然后在输出‘5’,此时微队列已经清空,所以执行宏队列输出‘0’.
*/