异步
并发:宏观概念,有两个任务 A、B,在一段时间内通过任务的切换,完成了两个任务
并行:微观概念,两核 CPU 同时完成那个两个任务
Generator
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
假如不传入参数,
var it = foo()
console.log(it.next()) // {value: 2 * (undefined + 1), done: false}
console.log(it.next()) // {value: undefined / 3, done: false}
console.log(it.next()) // {value: undefined + undefined + undefined, done: true}
传参数,next()传的参数将作为上一个 yield 的返回值
var it = foo(10)
console.log(it.next()) // {value: 11, done: false}
console.log(it.next(6)) // {value: 4, done: false}
console.log(it.next(4)) // {value: 26, done: true}
Promise
构造函数执行、then 函数执行的区别?
- 构造函数是立即执行的
- then 是异步的,调用 then 函数会返回一个新的 promise 对象,内部使用 return,会被包装成 resolve()
缺点:
- 不能取消
- 错误需要回调函数去捕捉?
async、await
async function test() {
return "test..."
}
async function test2() {
await test().then(res => {
console.log("res", res)
})
console.log("start")
}
test2()
普通函数前面加上 async
- 执行这个函数就会返回一个 promise 对象。
- 相当于将函数的返回值,包裹了一层 Promise.resolve(),跟 then 中处理返回值操作相当
缺点:没有依赖性的几个异步请求,还不如使用 Promise.all() ?
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
执行 b 函数,函数体有 await,await 会保留此时 a 的值 0,并将后面的表达式包裹一层 Promise.resolve()。执行函数体外的 a++,最后再执行 then 后面的异步
setTimeout、setInterval、requestAnimationFrame
前两个都不能保证在指定的延时后执行,比如在前边代码操作耗时较多时。可以计算当前时间,插值多少,动态设置延时时间,来达到按期执行的目的
另外 setInterval 有可能在耗时操作后,将多个回调函数同时执行,影响性能,所有尽量用 setTimeout 代替
requestAnimationFrame 自带函数节流,基本上 16.6ms,浏览器一帧执行一次,延时效果是精确的
手写 Promise
简版 Promise
实现目标:
const PENDING = "pending"
const RESOLVE = "resolve"
const REJECT = "reject"
function _Promise() {
}
new _Promise((resolve, reject) => {
resolve("success")
}).then(res => {
console.log("res", res)
})
new _Promise((resolve, reject) => {
setTimeout(() => {
reject("fail")
}, 100)
}).then(res => {
console.log("res", res)
})
function _Promise(fn) {
const _this = this
_this.state = PENDING
// 返回结果
_this.val = null
_this.resolveCbs = []
_this.rejectCbs = []
// 只有当状态是pendding时,状态才能改变
function resolve(val) {
setTimeout(() => {
if (_this.state === PENDING) {
_this.state = RESOLVE
_this.val = val
_this.resolveCbs.forEach(cb => cb(_this.val));
}
}, 0)
}
function reject(val) {
setTimeout(() => {
if (_this.state === PENDING) {
_this.state = REJECT
_this.val = val
_this.rejectCbs.forEach(cb => cb(_this.val));
}
}, 0)
}
// 执行传进来的函数,并传入resolve、reject
try {
fn(resolve, reject)
} catch(err) {
reject(err)
}
}
// 如果传入的函数是延时的,那么当前state是pedding,将两个回调都push进去
// 立即执行的函数,立即修改state,并执行回调
_Promise.prototype.then = function (resolveCb, rejectCd) {
const _this = this
resolveCb = typeof resolveCb === "function" ? resolveCb : val => val
rejectCd = typeof rejectCd === "function" ? rejectCd : val => {
throw new Error(val)
}
if (_this.state === PENDING) {
this.resolveCbs.push(resolveCb)
this.rejectCbs.push(rejectCd)
}
if (_this.state === RESOLVE) {
resolveCb(_this.val)
}
if (_this.state === REJECT) {
rejectCd(_this.val)
}
}
Promise/A+ ?
后边这一坨没看太懂....
_Promise.prototype.then = function (resolveCb, rejectCd) {
const _this = this
resolveCb = typeof resolveCb === "function" ? resolveCb : val => val
rejectCd = typeof rejectCd === "function" ? rejectCd : val => {
throw new Error(val)
}
if (_this.state === PENDING) {
let promise2
return promise2 = new _Promise((resolve, reject) => {
this.resolveCbs.push(() => {
try {
const x = resolveCb(_this.val)
resolutionProcedure(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
this.rejectCbs.push(() => {
try {
const x = rejectCd(_this.val)
resolutionProcedure(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
if (_this.state === RESOLVE) {
let promise2
return promise2 = new _Promise((resolve, reject) => {
setTimeout(() => {
try {
const x = resolveCb(_this.val)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (_this.state === REJECT) {
let promise2
return promise2 = new _Promise((resolve, reject) => {
setTimeout(() => {
try {
const x = rejectCd(_this.val)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
}
function resolutionProcedure(promise2, x, resolve, reject) {
// 返回值不能与结果相等,避免循环依赖
if (promise2 === x) {
return reject(new Error("err"))
}
if (x instanceof _Promise) {
x.then(function (res) {
resolutionProcedure(promise2, res, resolve, reject)
}, reject)
}
let called = false
if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
let then = x.then
if (typeof then === "function") {
then.call(x, y => {
if (called) {
return
}
called = true
resolutionProcedure(promise2, y, resolve, reject)
}, e => {
if (called) {
return
}
called = true
reject(e)
})
} else {
reject(x)
}
} catch (e) {
if (called) {
return
}
called = true
reject(e)
}
} else {
reject(x)
}
}
Event Loop
线程、进程
线程是进程的最小单元。比如浏览器打开一个 tab,就是开启了一个进程,这个进程又包含了 js 线程、http 请求线程、渲染线程等。当发起一个请求时,就创建了一个线程,请求结束,线程销毁。
浏览器每打开一个 tab 就是开启了一个进程,然而浏览器内核是多线程的,包括 js 引擎线程、DUI 渲染线程、定时器线程、事件触发线程、http 异步线程等。
执行栈
当执行一个函数的时候,js 会生成与这个函数相对应的 context 执行上下文,这个执行环境包含了函数内作用域、父的作用域、this、变量等。当很多函数被依次调用时,由于 js 是单线程,一次只能执行一个函数,所以就把这些函数排队放到栈中,也叫执行栈。每当要执行某个函数,就将函数入栈,执行完,出栈。

单线程、非阻塞
- 浏览器有多个线程,js 是单线程的,同时间只能做一件事。
- js 又是非阻塞的,当 js 执行到一段异步任务时,就是通过其他线程来完成的,比如网络请求、定时器,是分别交给定时器线程、http 请求线程来做。处理完成后,达到了触发条件,再将其回调添加到任务队列中,等待 js 主线程空置,再添加到执行栈执行。
- js 引擎线程和渲染线程是互斥的,js 运行的时候可能会阻断渲染线程。以防止 js 线程运行的时候,渲染线程还在工作。
宏任务、微任务
宏任务:script、定时器、netWork 微任务: Promise、process.nextTick(node.js)、MutationObserver(vue2.$nextTick实现)、Object.observe(已废弃)
事件循环
- 执行 js 代码,就是往执行栈放入函数,一段 script 脚本,就是宏任务的开始。
- 当遇到异步代码,先将这个异步代码 pending 挂起,比如一个网络请求,是在异步请求线程执行完,将回调放到任务队列中(宏任务队列、微任务队列)
- 当执行栈清空,会依次清空当前微任务队列。假如微任务中又产生了新的微任务,又会通过 Web apis,将其回调添加到微任务队列。直到清空微任务队列。
- 清空完微任务 就将在需要的时候假如队列中。当执行栈空了以后,就从任务队列中取出要执行的代码,放入执行栈执行。
先分析个简单的:
setTimeout(_ => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
- 代码都是在 script 标签里的,那么整个代码块就作为第一个宏任务,开始宏任务
- setTimeout 入栈,是定时器,出栈。webapi 辅助线程等到延时 0 秒后,将其回调放到宏任务队列的末尾
- new Promise 入栈,执行代码块。webapi 辅助线程将 resolve 回调放到微任务队列的末尾,输出
1,2 - 下次宏任务开始前,检查微任务队列,微任务队首出队,输出
3。清空微任务,渲染 ui - 执行下次宏任务队列,宏任务队首出队,输出
4
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
- 开始宏任务,输出
script start - 后执行 async1(), await 相当于把后面的执行结果包了一层 Promise.resolve,函数式立即执行的,输出
async2 end。后边的放到微任务队列末尾 - setTimeout 的回调,0 秒后放到宏任务队列末尾
- new Promise 立即执行,输出
Promise。同样将 then 的回调放到微任务末尾。 - 输出
script end。 - 本轮宏任务结束之前,先清空微任务队列,async1 函数的回调出队,输出
async1 end。 - promise1 回调出队,输出
promise1,将 promise2 回调假如到微任务末尾。 - 再次清空微任务队列,输出
promise2。GUI 渲染,本轮宏任务结束 - 执行下一步宏任务 setTimeout 回调出队,输出
setTimeout
script start, async2 end, Promise, script end, async1 end, promise1, promise2, setTimeout