JavaScript是单线程的语言,脚本内部只有一个线程执行代码,多个任务就按顺序依次执行。优点是安全和简单,缺点是遇到耗时任务就要等待耗时任务完成后,才能执行下面的操作。
为了解决耗时任务的等待,JavaScript将任务的执行分为两种模式:同步模式和异步模式
同步模式:任务会按顺序依次执行,当遇到大量耗时任务,后面的任务就会被延迟,这种延迟称为阻塞,阻塞会造成页面卡顿
异步模式:不会等待耗时任务,遇到异步任务就开启后立即执行下一个任务,耗时任务的后续逻辑通常通过回调函数来定义执行,代码执行顺序混乱
console.log(1)
setTimeout(function() {
console.log(4)
}, 2000)
setTimeout(function() {
console.log(3)
}, 1000)
console.log(2)
当遇到异步任务时创建任务,主线程(执行栈)继续执行后面的操作,另一个线程由js的执行环境提供,异步任务会由另一个线程执行。当主线程执行完本轮任务后,Event loop会检查消息队列中是否有回调执行。异步任务执行完后,会将异步任务的回调函数会按照执行完成顺序,放入消息队列中,当消息队列变化后,Event loop会取出消息队列头一个回调放入主线程中执行,循环执行完消息队列的回调,如果再遇到异步任务重复这个过程。
回调函数
回调函数是异步编程的基本
回调函数可以理解为一件想要去做的事情,由调用者定义好函数,交给执行者在某个时机去执行,把需要执行的操作操作放在函数里,将函数传入给执行者执行
Promise
是一种用来解决回调函数嵌套的规范,在ES2015中被标准化为规范
Promise是一个对象,用来表示一个异步任务执行的状态,有三种状态
- Pending:开始是等待状态
- Fulfilled:成功的状态,会触发onFulfilled
- Rejected:失败的状态,会触发onRejected
成功或者失败的状态一旦确认,就不可能再改变
const promise = new Promise(function(resolve, reject) {
// 这里是同步代码
// resolve执行表示异步任务成功
// reject执行表示异步任务失败
// resolve和reject最终只能执行一个
resolve(100)
// reject(new Error('reject')) // 失败
})
promise.then(function() {
// 成功的回调
}, function () {
// 失败的回调
})
const ajax = function (url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('aaa').then(function (res) {
console.log(res)
}, function (err) {
console.log(err)
})
Promise的链式调用
Promise对象调用then方法后会返回一个新的Promise对象,这个新的Promise对象可以继续调用then实现链式调用
后面的then方法是为上一个then返回的Promise对象注册回调
前一个then方法中回调函数的返回值会作为后面then方法回调的参数
return 后面可以是Promise对象,普通值(自动包装成Promise resolve 回调传入普通值),不写(自动包装成Promise resolve 回调传入null)
return 返回的如果是Promise对象就会等待Promise执行结束
链式调用的目的是为了解决回调函数嵌套的问题
new Promise((resolve, reject) => {
resolve()
})
.then()
.then()
.then()
// 回调嵌套
setTimeout(() => {
console.log('111')
var a = 10
setTimeout(() => {
console.log('222')
var b = 10
setTimeout(() => {
console.log('333')
var c = 10
console.log(a + b + c)
}, 1000)
}, 1000)
}, 1000)
//promise链式调用
new Promise((resolve,reject) => {
setTimeout(() => {
console.log('111')
var a = 10
resolve(a)
}, 1000)
}).then((res) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('222')
var b = 20
resolve(res + b)
}, 1000)
})
}).then((res) => {
setTimeout(() => {
console.log('333')
var c = 30
console.log(res + c)
}, 1000)
})
Promise异常处理
then方法中第二个参数回调(onRejected)就是Promise reject执行,除了then第二个参数外,还可以用catch来捕获reject
链式调用中当前then方法的onRejected不能捕获当前then方法中执行抛出的异常,因为当前then的onRejected是为上一个then,这时出现异常要用catch来捕获
catch最后在Promise链式调用的最后加上,可以捕获整个链式调用中出现的错误,避免then中缺少onRejected回调,导出异常没有被捕获
let promise1 = new Promise(function (resolve, reject) {
reject(new Error('reject'))
})
// 两种方式都可以
promise1.then(() => {}, (err) => console.log(err))
promise1.then(() => {}).catch(err => console.log(err))
let promise2 = new Promise(function (resolve, reject) {
resolve()
})
promise2.then(() => {
return new Promise(function (resolve, reject) {
throw new Error('error')
})
},
// onRejected不能捕获当前then返回的Promise的回调
(err) => console.log(err))
// catch可以捕获
promise2.then(() => {
return new Promise(function (resolve, reject) {
throw new Error('error')
})
}).catch(err => console.log(err))
Promise静态方法
Promise.resolve():返回一个Promise对象
如果传入是普通值,包装成Promise对象,resolve回调传入的值
传入一个Promise对象,返回传入的Promise对象
传入一个有 接受两个回调参数(onFulFilled,onRejected)的then方法的普通对象,会返回一个新的包装后的Promise对象,then方法会接受到原来对象调用onFulFilled和onRejected值
Promise.rejcet():返回一个Promise对象
无论传入什么参数都会作为reject的失败原因
Promise.all():返回一个Promise对象
传入一个数组,数组的元素是一个个Promise对象,会执行执行Promise对象,等待数组里面所有Promise对象完成后,返回的Promise对象才完成;如果全部Promise成功后拿到的结果是一个数组,数组里的值时对应每个Promise对象的执行结果;如果有其中一个执行失败就会得到一个失败的原因
Promise.race():返回一个Promise对象
传入一个数组,数组的元素是一个个Promise对象,会执行执行Promise对象,和Promise.all不同,Promise.race只会等待第一个先完成Promise对象返回,第一个返回的结果就是返回新的Promise对象结果
Promise回调执行时序
Promise回调是作为微任务,微任务会在当前宏任务执行完后立即执行,下一个宏任务之前
console.log('start')
// setTimeout是宏任务 在当前宏任务之后
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve()
// then的回调是微任务 在当前宏任务之后 下一个宏任务之前
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})console.log('end')
// start
// end
// promise 1
// promise 2
// setTimeout
Generator生成器函数
function * foo () {
console.log('start')
try {
const res = yield 'foo'
// 得到next()传入的值 bar
console.log(res) } catch(e) {
console.log(e) // error
}
}
const generator = foo()
// 调用generator.next() 会执行到yield的位置
// console.log('start')const result = generator.next()
// next 返回一个对象 { value: 'foo', done: false}
// value 是 yield 后面的值 done 表示是否generator执行完成
console.log(result)
// next可以传值 传入的值会作为 上一个yield的返回值
generator.next('bar')
// 调用throw可以在generator抛出异常
generator.throw(new Error('error'))
生成器执行器
function * main () {
try {
const user = yield ajax('user')
console.log(user)
const posts = yield ajax('posts') console.log(posts)
const url = yield ajax('url') console.log(url)
} catch(e) {
console.log(e)
}
}
const g = main()
function handleResult (result) {
if (result.next) {
// 生成器执行结束
return
} else {
// ajax 返回的Promise对象
result.value.then(data => {
// 将当前执行结果data传回给生成器函数 再执行
handleResult(g.next(data))
}, error => {
// Promise执行失败 生成器throw让生成器捕获异常
g.throw(error)
})
}
}
handleResult(g.next())
Async/awit 语法糖
Async和awit是generator生成器函数的语法糖,简化了生成器函数的执行器,并且会返回一个Promise对象
awit只能用在async的函数里面
// 将生成器函数的 * 去掉 在function前面加async
async function main () {
try {
// 将yield换成awit
const user = awit ajax('user')
console.log(user)
const posts = awit ajax('posts')
console.log(posts)
const url = awit ajax('url')
console.log(url)
} catch(e) {
console.log(e)
}
}
// 会自动执行
// 执行效果和generator生成器函数一样效果
// 并且返回一个Promise对象
const promise = main()
// 执行完后调用then的回调
promise.then()