Js单线程的意思就是JS执行环境中负责执行代码的线程只有一个
同步模式
同步模式是指后一个任务必须要等前一个任务结束才能开始执行,
在代码执行时,会将函数的调用压入调用栈,在函数执行后将此函数从调用栈中清除,如果在执行代码期间有一段代码执行时间过长就会造成阻塞现象,会使页面出现卡顿、卡死。
异步执行模式
异步执行模式不会等待一个任务结束才去执行下一个任务,对于耗时操作它都会开启之后立即往后执行任务,后续逻辑一般通过回调函数方式定义
const {log} = console
log('global begin')
setTimeout(function timer1 () {
log('timer1 invoke')
},1800)
setTimeout(function timer2 () {
log('timer2 invoke')
setTimeout(function inner () {
log('inner invoke')
},1000)
},1000)
log('global end') //
// ->global begin
//global end
//timer2 invoke
//timer1 invoke
//inner invoke
首先加载整体代码在调用栈中压入一个匿名的全局调用,对于console.log这样的同步API和之前一样是先入栈然后执行 打印出信息后弹栈,随后遇到setTimeOut这样一个调用,将他压入调用栈,但是这个函数内部是一个异步调用,他的内部API是为timer1这个函数开启了一个倒计时器,这个倒计时器是单独工作的并不会受到js线程的影响,开启倒计时器之后,setTimeOut调用结束,弹出栈继续向后执行,又遇到一个setTimeOut调用同理先入栈然后开启计时器弹出栈最后执行console.log出栈后整个匿名调用结束弹出栈,调用栈被清空。Event loop的作用是监听调用栈和消息队列,当调用栈已经被清空了结束调用那么event loop就会将消息队列中的第一个回调函数压入调用栈,在上述代码中首先timer2先结束将他放入消息队列中的第一位随后timer1进如队列第二位此时,事件循环(event loop)监听到消息队列发生变化,此时将timer2—消息队列中的第一个回调压入调用栈以此类推去执行
整个异步执行模式都是通过消息队列和事件循环去实现的,JS单线程只是执行码的线程是单线程,浏览器却不是单线程,执行那些API的时候用的是另一个线程执行完后将他们放入消息队列
回调函数
回调函数是所有异步编程的根基
回调函数可以理解为你想要做的事情,把这些事情交给异步函数的执行者,让他知道在什么事情结束后去执行这些回调函数。
举个栗子:我想要给一张桌子刷漆,但是我现在没有油漆,我现在去拜托你去帮我买一桶油漆,但是我不能干等着你,我还要干别的事情,所以我把怎么刷这个桌子的方法一并交给你,我就去干别的事情了 ,等你买油漆回来就可以按照方法自己把桌子刷好。
在这个例子中 我就是异步函数调用者,你就是执行者,怎么刷漆是会带哦函数,找你帮我买桶油漆就是调用。回调函数的用法就是让你的函数作为参数去传递
Promise
Promise是一种更优的异步编程统一方案
为了避免回调地狱的出现,CommonJs社区提供了一个Promise的规范,后来在ES2015中被标准化
Promise就是一个对象用来去表示一个异步任务在结束过后是成功还是失败
它有三种状态:Pending(等待)、Fulfilled(成功)、Rejected(失败)。成功后去执行onFulfilled回调,失败则执行onRejected回调
Promise的基本用法
Promise的回调会进入消息队列在同步代码执行完之后执行
const promise = new Promise(function (resolve,reject) {
// 这个函数参数适用于兑现承诺的逻辑
resolve(100) // 这个函数用于修改承诺状态为成功,里面返回的值作为成功回调的参数值
// reject(new Error('promise rejected')) // 这个函数用于修改承诺状态为失败 传入一个对象
})// resolve 和 reject 只能存在一个 因为只有Promise只能存在一种状态
promise.then(function (value) {
console.log('resolved',value)
},function (error) {
console.log('rejected',error)
}) // promise.then去执行回调函数 第一个参数是成功的回调函数 第二个是失败的回调函数
Promise的使用案例
案例:使用Promise封装AJAX
function ajax(url) {
return new Promise(function (resolve,reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET',url) //请求方式是get 请求的地址是url
xhr.response = 'json' //相应方式为json 返回来的就是json对象
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject( new Error (this.statusText))
} //请求成功调用resolve,反之调用reject传入一个错误对象信息是错误文本
} //请求完成过后去执行的事件 就是之前状态等于4的事件
xhr.send() // 去执行请求
})
}
ajax('/api/users.json').then(function (res) {
console.log('res',res)
},function (err) {
console.log('error',err)
})
Promise的误区
Promise的本质也是使用回调函数的方式来定义异步任务结束后所执行的任务
嵌套使用的方式是使用Promise最常见的错误
正确方法是借助于Promise then 方法的链式调用的特点尽量保证异步任务的扁平化的特点
Promise的链式调用
Promise中的then方法都是为上一个then放回的promise对象添加状态过后的回调
- Promise对象的then方法会返回一个全新的Promise对象
- 后面的then方法就是为上一个then返回的Promise注册回调
- 前面then方法中回调函数的返回值会作为后面then方法中的参数
- 如果回调中返回的是Promise,那么后面then方法的回调会等待他的结束
Promise异常处理
ajax('/api/users.json').then(function (res) {
console.log('res',res)
}).catch( function(err) {
console.log('err',err)
})
用catch去捕获Promise的异常 因为在之前的onReject的方式中如果第一个成功的回调返回了一个新的Promise对象而这个Promise对象返回了一个错误的回调,那么他是捕获不到异常的,而catch可以捕获到这个链式调用整条链上所有的异常
Promise的静态方法
Promise.resolve():它可以吧一个值转化成fulfilled的Promise对象,若在里面传入一个promise对象那么它会被原样返回
Promise.reject():它可以快速创建一个一定是失败的Promise对象
Promise.resolve('foo').then(function (res) {
console.log('res',res)
})
Promise.reject(new Error('gg')).catch(function(error) {
console.log(error)
})
Promise的并行执行
Promise.all和Promise.race
Promise.all是等所有里面的promise对象执行完成后执行,二Promise.race()则是等待他第一个Promise对象执行完成后执行。所以如下所示Promise.race()经常用于ajax的超时控制
var promise = Promise.all ([
ajax('/api/1'),
ajax('/api/2'),
ajax('/api/3'),
ajax('/api/4')
]) //Promise.all接收一个数组里面是要并行执行的Promise对象,并返回一个新的Promise对象
promise.then(function (value) {
console.log(value)
}).catch(function (error) {
console.log(error)
}) //只有这个里面所有Promise都返回resolve才会执行onFulfilled回调否则就会执行onReject
const request = ajax('wwww')
const timer = new Promise((resolve,reject) => {
setTimeout(() => {reject(new Error('gg'))},5000)
})
Promise.race([
request,timer
]).then(value => {
console.log(value)
}).catch(error => {
console.log(error)
})
Promise执行时序(微任务vs宏任务)
回调队列中的任务叫做宏任务,宏任务执行过程中可以临时加上额外的需求,宏任务可以选择作为一个新的宏任务进到队列中排队也可以作为当前任务的一个微任务即直接在当前任务结束之后直接执行,而不是到整个队列中的末尾重新排队,Promise的回调就是作为一个微任务执行的,他会在本轮调用的末尾直接执行。微任务就是用来提高整体的响应能力,大部分都是作为宏任务只有Promise对象和MutationObserver是作为微任务进行执行
//微任务
console.log('global start')
setTimeout(() => {
console.log('setTimeOut')
},0)
Promise.resolve()
.then(value => {
console.log('promise')
}).then(value => {
console.log('promise 1')
}).then(value => {
console.log('promise 2')
}).then(value => {
console.log('promise 3')
})
console.log('global end')
/*global start
promise-sample.js:76 global end
promise-sample.js:68 promise
promise-sample.js:70 promise 1
promise-sample.js:72 promise 2
promise-sample.js:74 promise 3
promise-sample.js:63 setTimeOut
*/
Generator异步调用
function * main () {
const users = yield ajax('kkkk')
console.log(users)
const posts = yield ajax('llll')
}
const g = main()
const result = g.next() //获取的result内的值为成功的Promise对象
result.value.then(data => {
const result2 = g.next(data) //再一次返回一个Promise对象
if (result2.done === done) return //若状态为done则返回
result2.value.then( data => {
})
})
以递归的形式去执行
function * main () {
try {
const users = yield ajax('kkkk')
console.log(users)
const posts = yield ajax('llll')
} catch(e) {
console.log(e)
} //用try catch捕获生成器中间的异常
}
const g = main()
function handleResult (result) {
if(result.done) return //生成器函数结束
result.value.then(data => {
handleResult(g.next(data))
},error => {
g.throw(error)
}) //用then后面的error捕获异常
}
handleResult(g.next())
Async/Await 语法糖(语言层面的异步编程标准)
它的语法和Generator生成器十分类似 在函数声明前加上async在函数中将yield换成await,在执行async函数之后会返回一个promise对象
async function main () {
try {
const users = await ajax('kkkk')
console.log(users)
const posts = await ajax('llll')
} catch(e) {
console.log(e)
} //用try catch捕获生成器中间的异常
}
const promise = main()
promise.then(() => {
console.log('completed')
})