js为什么需要异步,因为异步的本质是单线程,异步又是基于回调实现的
setTimeout
传参
- 如果传入立即执行函数,会被立即执行。
- 给执行函数传参可以用字符串形式,或者传参
function fnHello(name) {
console.log('hello, ' + name)
}
setTimeout(fnHello('tony'), 2000)
setTimeout("fnHello('jack')", 2000)
setTimeout(fnHello, 2000, 'li')
面试题
var b = '2'
function abc() {
let b = 1
++b
setTimeout(() => {
test('fun test')
}, 0)
setTimeout(test('test fun'), 1000)
console.log(b)
function test(str) {
this.b++
console.log(str)
console.log(this.b++)
}
}
abc()
解析执行结果
- test fun:setTimeout第一个参数,是个立即执行的函数,打印传入的字符串
- 3:test执行函数没有任何调用者,this指向window,全局作用域的b++,字符串被隐式转换成Number。函数执行完b为4
- 2:函数作用域++b的结果
- fun test:同步代码执行完,执行栈清空,从宏任务队列中取出回调压栈执行
- 5:全局作用域的b++,4 => 5
Promise
把嵌套的回调变成链式调用,类似一节一节的管道。resolve会进到then回调,reject进到catch的回调
三种状态
const p1 = Promise.resolve(1).then(() => {
return 'resolve.suc'
})
console.log('p1', p1)
const p2 = Promise.reject(2).then(() => {
return 'reject1.err'
})
console.log('p2', p2)
const p3 = Promise.reject(3).then(() => {
return 'reject2.err'
}).catch(err => {
console.log('p3', err)
})
console.log('p3', p3)
// p1和p3的最终状态都是fulfilled,p2是rejected
异常捕获
- then和catch内部没有报错,返回resolve的promise,进到then
- 如果有报错,都会返回reject的promise,进到catch
const p4 = Promise.resolve(4)
.then(() => {
throw new Error('p4.then.throw err ----- ')
})
.catch((err) => {
console.log('p4.catch', err)
})
// 输出:p4.catch Error: p4.then.throw err-----
const p5 = Promise.resolve(5)
.then(() => {
throw new Error('p5.then.throw err ----- ')
})
.catch((err1) => {
console.log('p5.catch1 ---', err1)
throw new Error('p5.catch1.throw err ----- ')
})
.then((r) => {
console.log('p5.then2', r)
})
.catch((err2) => {
console.log('p5.catch2 ---', err2)
})
// p5.catch1 --- Error: p5.then.throw err -----
// p5.catch2 --- Error: p5.catch1.throw err -----
面试题
// 第一题
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 第二题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 第三题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => {
console.log(3)
})
解析执行结果
第一题
- 1:第1个then,内部没有报错进到下一个then
- 3:第2个then
第二题
- 1:第1个then,内部报错进到catch
- 2:第1个catch,内部没有报错进到下一个then
- 3:第2个then
第三题
- 1:第1个then,内部报错进到catch
- 2:第1个catch,内部没有报错进到下一个then。后边是catch不是then,所以不执行
async/await
Generator 函数的语法糖。可以用同步语法来写异步了。
- 执行async函数,返回的都是promise对象
- await相当于promise.then
- try/catch可以捕获异常,代替了catch
async function fn1() {
return 100 // 相当于 Promise.resolve(100)
}
const r1 = fn1();
// await 相当于then
!(async function () {
const r2 = await Promise.resolve(200)
console.log("r2", r2)
})()
// 直接返回值,相当于Promise.resolve(300),同上
!(async function () {
const r3 = await 300
console.log("r3", r3)
})()
// await可以接收promise对象
!(async function () {
const r4 = await fn1()
console.log("r4", r4)
})()
// try...catch相当于catch
// await后的都会被放到then里,当接收到reject的promise,会进到catch,所以await后的代码不会执行
!(async function () {
const p5 = Promise.reject("r5.err")
try {
const r5 = await p5
console.log("r5", r5)
} catch (err) {
console.log("r5.catch", err) // 相当于catch
}
})()
循环中使用异步
根据以下代码,实现依次延时打印结果
const arr = [1, 2, 3]
// 延时计算值的平方
function squ(n) {
return new Promise(resolve => {
setTimeout(() => {
resolve(n * n);
}, 1000)
})
}
使用forEach
arr.forEach(async n => {
const r1 = await squ(n)
console.log("r1", r1)
})
// 1秒后,三个结果一次输出,这不是期望的结果
// forEach实现原理
Array.prototype.customForEach = function (fn) {
for (var i = 0; i < this.length; i++) {
fn(this[i], i)
}
}
// 尽管定义的时候是加了async/await,但是内部还是一个没有添加await的执行函数,所以会同步执行,同步输出结果
使用for循环
// 异步执行
!(async function () {
for (let i = 0; i < arr.length; i++) {
const r2 = await squ(arr[i]);
console.log("for", r2)
}
})()
// 异步执行
!(async function () {
for (let i in arr) {
const r2 = await squ(arr[i]);
console.log("for in", r2)
}
})()
// 异步执行
!(async function () {
for (let i of arr) {
const r2 = await squ(i);
console.log("for of", r2)
}
})()
// 这三种for循环都是每间隔1s输出一次结果
面试题
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
await async3()
console.log('async1 end 2')
}
async function async2() {
console.log('async2')
}
async function async3() {
console.log('async3')
}
console.log('script start')
async1()
console.log('script end')
解析执行结果
- script start:同步代码直接打印
- async1 start:async1() 会立即执行函数(函数前面加上await后,执行可以拆解为两步:1.执行函数,2.await)
- async2:await async2() 立即执行,并将后边所有代码块封装起来(类似全放到Promise.then里),等待同步任务执行完。到此async1函数暂时执行完了
- script end:同步代码,清空调用栈
- async1 end:调用栈空了,从微任务队列中,取出回调压栈执行。
- async3:同第3步
- async1 end 2:同第5步
宏任务和微任务
- 宏任务:setTimeout setInterval DOM 事件 ajax
- 微任务:Promise
<!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>
<div id="container"></div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1).append($p2).append($p3)
alert('p.length:' + $('#container').children().length)
Promise.resolve().then(() => {
alert('Promise.resolve')
})
setTimeout(function () {
alert('setTimeout')
})
</script>
</body>
</html>
js执行和dom渲染是用的同一线程,使用alert来阻断js执行,也会阻断dom渲染。可以判断各任务执行的优先级。 解析执行结果:
- 第1个alert弹出p的长度是3,但页面上没有看到三个p元素,说明dom的渲染被阻断了
- 第2个alert弹出Promise,还是没看到三个p元素,dom的渲染还在处于被阻断的状态
- 第3个alert弹出setTimeout,页面出现了三个p元素,说明dom渲染结束
由此可以得到执行顺序:Promise > dom 渲染 > 定时器
测试题
async function fn() {
return 100
}
!(async function () {
const a = fn()
const b = await fn()
console.log("a", a)
console.log("b", b)
})()
// 输出:promise 100
// async函数返回的是promise对象,加上await就是promise.then的返回结果
!(async function () {
console.log('start')
const a = await 100
console.log('a', a)
const b = await Promise.resolve(200)
console.log('b', b)
const c = await Promise.reject(300)
console.log('c', c)
console.log('end')
})()
// 输出:start 100 200 err(300)
// reject的promise会走到catch,所以async接收reject会进到try的catch中
// 由于后边两行代码都被then的回调中,所以不输出
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 输出:100 400 300 200
// promise和定时器的执行顺序
面试题
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
解析执行结果
- script start:同步代码
- async1 start:async1()立即执行函数
- async2:await async2()立即执行函数,后边代码快都封装起来,放到微任务队列末尾
- promise1:new Promise()传入的函数也会立即执行,resolve()后该实例状态变为fulfilled,将then回调放到微任务队列末尾
- script end:同步代码
- async1 end:执行栈空了,从微任务队列取出第3步的回调
- promise2:执行栈又空了,从微任务队列取出第4步的回调
- setTimeout:宏任务总是在同步任务、微任务、dom渲染之后执行
实现Promise
class MyPromise {
// 三种状态:pending/fulfilled/rejected
status = "pending";
// 记录当前Promise的值,给新的Promise
value = undefined; // 执行结果
reason = undefined; // 错误信息
// 存贮在pending状态下,then注册的回调
resolveCallbacks = [];
rejectCallbacks = [];
/**
* 执行函数,捕获异常。修改实例状态
* @param {自定义函数} fn
*/
constructor(fn) {
const resolveHandler = value => {
this.value = value;
this.status = "fulfilled";
this.resolveCallbacks.forEach(callback => callback(this.value))
}
const rejectHandler = reason => {
this.reason = reason;
this.status = "rejected";
this.rejectCallbacks.forEach(callback => callback(this.reason))
}
try {
fn(resolveHandler, rejectHandler);
} catch (err) {
rejectHandler(err)
}
}
/**
* then始终返回新的Promise实例
* @param {成功回调} fn1
* @param {失败回调} fn2
* @returns
*/
then(fn1, fn2) {
// 处理兼容,保证都是可执行的函数
fn1 = typeof fn1 === "function" ? fn1 : fn1 => fn1;
fn2 = typeof fn2 === "function" ? fn2 : fn2 => fn2;
return new MyPromise((resolve, reject) => {
if (this.status === "fulfilled") {
try {
// 执行成功回调的结果,传给当前Promise,状态变为resolve
resolve(fn1(this.value))
} catch (err) {
// 捕获错误,当前Promise状态变为rejected
reject(err)
}
} else if (this.status === "rejected") {
try {
// 执行失败回调的结果,传给当前Promise,状态变为reject
reject(fn2(this.reason))
} catch (err) {
// 捕获错误,当前Promise状态变为rejected
reject(err)
}
} else if (this.status === "pending") {
// 当实例还在定时器,或者ajax时,拿不到最新值。先存起来,等状态变了后执行
this.resolveCallbacks.push(() => {
try {
resolve(fn1(this.value))
} catch (err) {
reject(err)
}
})
this.rejectCallbacks.push(() => {
try {
resolve(fn2(this.reason))
} catch (err) {
reject(err)
}
})
}
})
}
catch(fn1) {
return this.then(null, fn1)
}
static resolve(param) {
return new MyPromise((resolve, reject) => resolve(param))
}
static reject(param) {
return new MyPromise((resolve, reject) => reject(param))
}
/**
* 全部执行完或者有一个失败就返回
* @param {实例数组} promiseList
* @returns
*/
static all(promiseList = []) {
return new MyPromise((resolve, reject) => {
let cur = 0
const res = [];
promiseList.forEach(p => {
p.then(r => {
res.push(r)
cur++
if (cur === promiseList.length) {
resolve(res)
}
}).catch(err => {
reject(err)
})
})
})
}
/**
* 赛跑,有一个结束就返回
* @param {*} promiseList
* @returns
*/
static race(promiseList = []) {
let isOver = false
return new MyPromise((resolve, reject) => {
promiseList.forEach(p => {
p.then(r => {
if (!isOver) {
resolve(r)
}
isOver = true
}).catch(err => {
reject(err)
})
})
})
}
}
测试MyPromise
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('2s.end ---')
}, 2000)
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('5s.end ---')
}, 5000)
})
const res = MyPromise.all([
p1,
MyPromise.resolve(100),
p2
// MyPromise.resolve(200)
// MyPromise.reject('200 --- ')
])
res.then((r) => {
// console.log('all.r', r)
}).catch((err) => {
console.log('all.err', err)
})
const res2 = MyPromise.race([
p1,
// MyPromise.resolve(100),
p2
// MyPromise.reject('200 --- ')
])
res2.then((r) => {
// console.log('race.r', r)
}).catch((err) => {
console.log('race.err', err)
})
const p0 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise.3s')
}, 3000)
})
}