共计 8246 个字符,预计需要花费 21 分钟才能阅读完成。
原文地址
掘金
欢送 github star
大家好,我是林一一,异步编程在 JS 中是无奈防止的,也是面试必问的。本文应用通俗易懂的语言解析异步编程中的原理,开始浏览吧😁
思维导图
一、定时器
定时器:设定一个定时器,到了设定工夫,浏览器会把对应的办法执行。每一个定时器执行后都会有一个编号返回,每个定时器编号不一样。
1. 设定定时器
-
setTimeout([function], [interval])
function 都是在达到设定工夫后才执行。且执行一次
let count = 1 let timer = setTimeout(function(){ count++ console.log(count) // 2 }, 1000) console.log(timer) // 1
-
setInterval([function], [interval])
在设定工夫内执行,不被动进行的状况下始终执行。
let count = 1 let timer = setInterval(function(){ count++ console.log(count) // 2 }, 1000) console.log(timer) // 1
- 2
- 3
- 4
-
…
*/### 2. 革除定时器
-
如何革除定时器?
只须要定时器的返回值编号革除即可。
let count = 1 let timer = setInterval(function(){ count++ console.log(count) // count == 3 ? clearTimeout(timer) : null count == 3 ? clearInterval(timer) : null }, 1000)
二、异步编程的原理
先来看一个小例子
let a = 0
setTimeout(() =>{console.log('a', ++a)
}, 0)
console.log(a)
/* 输入
* 0
* 1
*/
下面的例子中,
setTimeout
是异步的,浏览器会将异步的代码退出到工作队列中,等到同步的代码执行实现后才执行异步的代码
1. 同步
JS 是单线程的,代码至上而下执行时遇到同步的代码须要先执行完才能够进行下一步工作。比方循环等
2. 异步
所有须要期待的工作都是异步的。遇到异步代码时,不须要期待而是间接异步工作放入工作队列,等到前面的工作实现后,才会返回来执行没有实现异步的代码。比方事件绑定,所有定时器,ajax 的异步解决,局部回调函数,浏览器的渲染过程等等。
let a = 0
setTimeout(() =>{console.log('a', ++a)
}, 0)
console.log(a)
while(true){}
下面的代码死循环了,即便定时器的工夫到了也不会执行。因为同步的代码没有执行完一步就不会执行。
三、promise
1. 基本概念
Promise
只是一个治理异步编程的类,自身是同步。Promise
有三个状态pending/fulfilled/rejected
,三个状态只有两个状态呈现要么胜利要么失败。new Promise()
时必须要传入回调函数executor
,否则报错。其中回调函数中有两个参数resolve, reject
,这两个参数可不写。
pending
:初始化状态,开始执行异步的工作,只有执行 new,new Promise(()=>{})
,promise 的状态就会变成pending
fulfilled
:胜利状态,执行resolve()
。-
rejected
:失败状态, 执行rejected()
。
先看一个小栗子。new Promise(()=> {setTimeout(()=> {console.log(1) }, 0) console.log(2) }).then() console.log(3)
- 2
- 3
-
1
*/> 创立一个新的 `Promise` 的实例也就是 `new` 这个过程中会把 `Promise 中 ` 的函数先执行(不分明 `new` 创立实例的过程中产生了什么能够看看这篇 [面试 | 你不得不懂得 JS 原型和原型链](https://juejin.cn/post/6938590449674223624#heading-6))。函数体内有异步操作的仍会退出工作队列,等到同步执行实现后才执行异步工作,比方函数体内的 `setTimeout 函数 `。所以输入的后果就是 `2,3,1`。
再来看一个小栗子
const promise = new Promise((resolve, reject) => {resolve('success1')
reject('error')
resolve('success2')
})
promise.then((res) => {console.log('then:', res)
}).catch((err) => {console.log('catch:', err)
}
// then: success1
promise 的状态只能扭转一次,最初的状态不是
fulfilled
就是rejected
。也就是说同一个promise
中的resolve()/ reject()
只能执行一个且一次。
2. promise 是怎么治理异步的
promise
参数的回调函数体内接管两个参数resolve/ reject
,这两个参数能够作为两个回调函数。
resolve()
:是异步操作执行胜利后执行,promise 的状态变成了fulfilled
,能够提供返回值,在.then()
中第一个参数接管reject()
:异步操作执行失败后执行,promise 的状态变成了rejected
,能够提供返回值,在.then()
中第二个参数接管resolve()
和reject()
中只能传递一个参数。promise 的状态产生扭转后不会再变动。resolve() 和 reject() 是异步操作
,执行这两个办法时,会先执行resolve/reject
上面的同步代码,等到主工作为空时,再去调用resolve/reject
把寄存的办法执行。Promise
对象上有公有属性Promise.resolve()/ Promise.reject()
等。
举一个没什么意义的小栗子
new Promise((resolve, reject)=> {setTimeout(()=> {console.log('林')
resolve('ok')
// reject('fail')
console.log('一一')
}, 0)
}).then( res => {console.log('status:', res)
}, res => {console.log('status:', res)
})
// 林
// 一一
// ok
下面的栗子间接看出
resolve/reject
是异步的。
3. promise.then(onfulfilled, onrejected) / promise.catch()
-
promise.then(onfulfilled, onrejected)
promise.then()
办法中有两个参数,别离对应着 promise 的两种不同的状态,fulfilled, rejected
。对应的状态执行对应的办法。promise.then()
中的参数是函数,如果传递的不是函数就会造成值穿透
,也就是resolve()/reject()
的返回值会.then()
中接管。promise.then()
可能链式的调用,可能链式调用的起因不是.then()
办法中有return this
,而是每一个.then()
办法中都会返回一个新的Promise
实例。
-
promise.catch()
promise.catch()
是promise.then()
第二个参数的简便写法,也就是用来捕捉reject()
执行后的rejected
状态。.catch()
也能够实现链式调用,起因和.then()
办法一样都是返回了一个新的promise
。
.then()/.catch()
中的返回值都不能是 promise
本人自身的实例,因为会造成死循环
热身题
1. 举一个小栗子
Promise.resolve(1)
.then((res) => {console.log(res)
return 2
}).catch(err => {console.log(err)
}).then( res => {console.log(res)
})
// 1 2
最终输入
1 2
,
2. 举一个值穿透的小栗子
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// 等价于
// Promise.resolve(1)
// .then(console.log)
3. then()/.catch() 内不能返回本人自身的 promise 实例,举一个栗子
let pro = Promise.resolve()
.then(() => {console.log('promise', pro)
return pro
})
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
创立的
promise
实例是pro
, 返回值就不能是pro
, 否则会造成死循环。
5. Promise.all([promise1, promise2…])
Promise.all()
中须要期待参数中所有promise
的状态都胜利才执行回调的.then()
,如果有一个是失败的那么就执行.catch()
。接管的参数是一个蕴含promise
实例的数组,.all()
这个办法的返回值也是一个新的promise
实例。
- 全副执行胜利回调
.then()
接管到的就是一个数组 -
如果有执行失败的
promise
状态,回调.catch
中就会捕捉到执行失败的promise
,promise.all()
执行完结。var p1 = Promise.resolve(1) var p2 = Promise.resolve(2) var p3 = Promise.resolve(3) let pro = Promise.all([p1, p2, p3]) .then(res => {console.log(res) // [1, 2, 3] }) .catch( err => {console.log(err) }) console.log(pro) // Promise {<pending>}
全副执行胜利,那么
.then()
获取到的值就是resolve()
的返回值数组。6. Promise.race()
.race()
的作用也是接管一组异步工作,而后并行执行异步工作,只保留取一个最快执行实现的异步操作的后果,其余的办法仍在执行,不过执行后果会被摈弃。 -
接管的是一组数组,只获取一个最快执行实现
resolve()/rejected()
的返回值,返回值不是一个数组var p1 = new Promise(function(resolve, reject) {setTimeout(reject, 500, "one"); }); var p2 = new Promise(function(resolve, reject) {setTimeout(resolve, 100, "two"); });
Promise.race([p1, p2]).then(function(value) {
console.log(value); // “two”
});
> 两个都实现,但 p2 更快
### 7. Promise.finally()
> `.finally` 办法也是返回一个 `Promise`,他在 `Promise` 完结的时候,无论后果为 `resolved` 还是 `rejected`,都会执行外面的回调函数。
let promise = new Promise((resolve, reject) => {
reject('error')
}).then(res => {
console.log('then', res)
return res
}).catch(err => {
console.log('catch', err) // `catch error`
return err
}).finally(() => {
console.log('finally') //'finally'
})
### 热身题
#### 热身题 1
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
// 1, 2, 4, 3
> 因为 `resolve()` 是异步的,`promise.then` 也是异步的,在没有获取到 `resolve()` 的 `fulfilled` 状态时 `.then()` 不会执行。#### 热身题 2
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() – start)
})
promise.then((res) => {
console.log(res, Date.now() – start)
})
/* 输入
- once
- success 1002
-
success 1002
*/> `promise.then()` 即便有多个,然而 `resolve()/reject()` 后的调用时同时执行的。所以同时输入了 `success 1002`
三、async 和 await
先来看一个栗子
function fn(){return new Promise((resolve, reject) => {setTimeout( () => {Math.random() < 0.5 ? resolve('resolve 001') : reject('reject 002')
}, 0)
})
}
async function get() {let res = await fn()
console.log(res)
console.log(1212)
}
get()
1. async
async
和await
是 ES7 中减少来对promise
操作的办法,是 ES7 系列提供的语法糖,await
不能独自应用肯定要联合async
来应用。async
会返回一个promise
对象,async
函数调用不会造成代码的阻塞
2. await
await
是用来期待获取
一个promise
的resolve/reject
的执行后果,像下面的let res = await fn()
是先把fn()
执行后,来获取resolve/reject
返回的后果,不过await
前面也能够不跟着一个promise
,然而这样写就没有意义了。await 或 await fn()
这个操作不是同步的,而是异步的。await
上面的代码不会执行,而是移入到工作队列期待区,等到主栈中的其余工作实现且fn()
中的promise
将后果返回,await
上面的代码才能够从新回到主栈中执行。await
能够使promise
的操作更加像同步的代码。- 如果 await 等到的获取后果是
reject()
返回的,那么await
上面的代码,就不会再执行,因为曾经报错了。
举一个小栗子, 阐明 async/await 是语法糖
async function async1() {console.log('async1 start');
await async2();
console.log('async1 end');
}
// 下面的代码等价于 ==>
async function async1() {console.log('async1 start');
Promise.resolve(async2()).then(() => {console.log('async1 end')
})
}
思考
热身 1,await 是同步吗?,求输入的后果
console.log(1)
function fn(){return new Promise((resolve, reject) => {console.log(5)
resolve('resolve 001')
console.log(6)
})
}
async function get() {console.log(2)
let res = await fn()
console.log(res)
console.log(3)
}
get()
console.log(4)
//1, 2, 5, 6, 4, resolve 001, 3
await
是异步的同时会让出线程,fn()
执行后线程开始让出,那么await
的前面的代码不会立刻执行会先到主栈的期待区,主栈中console.log(4)
执行后,再回来执行await
处的代码。
2. 热身 2,求输入后果
console.log(1)
async function get() {console.log(2)
let res = await 200
console.log(res)
console.log(3)
}
get()
console.log(4)
// 1, 2, 4, 200, 3
依据下面一题
await
异步代码的起因,能够容易的剖析出答案。同时阐明await
前面也能够跟着一个非promise
的实例。
四、经典面试题
1. promise 的优缺点 / 为什么应用 promise?
- 长处:promise 能够解决回调天堂,promise 大大加强了嵌套函数的可读性和可维护性,
- 毛病:无奈勾销 Promise,谬误须要通过回调函数来捕捉;如果不设置回调函数,Promise 外部抛出的谬误,不会反映到内部;当处于 pending(期待)状态时,无奈得悉目前停顿到哪一个阶段,是刚刚开始还是行将实现
2. setTimeout、Promise、Async/Await 的区别
- setTimeout: setTimeout 的回调函数放到宏工作队列里,等到执行栈清空当前执行
- Promise: Promise 自身是同步的立刻执行函数,当在 executor 中执行 resolve 或者 reject 的时候,此时是异步操作,会先执行 then/catch 等,当主栈实现时,才会去调用 resolve/reject 办法中寄存的办法。
- async: async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作实现,再执行函数体内前面的语句。能够了解为,是让出了线程,跳出了 async 函数体。
3. 实现一个 sleep 函数,比方 sleep(1000) 意味着期待 1000 毫秒。
function sleep1(time) {
return new Promise(resolve => {setTimeout(() => {resolve();
}, time);
})
}
sleep1(1000).then(() => console.log("sleep1"));
4. Promise 构造函数是同步执行还是异步执行,那么 then 办法呢
Promise
是同步的,执行new promise(callback)
时回调函数callback
就会被立刻执行,then()
办法是异步的。
const promise = new Promise((resolve, reject) => {console.log(1)
resolve()
console.log(2)
})
promise.then(() => {console.log(3)
})
console.log(4)
1243,promise 构造函数是同步执行的,then 办法是异步执行的
5. 介绍下 Promise.all 应用、原理实现及错误处理
const p = Promise.all([p1, p2, p3]);
Promise.all 办法承受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用上面讲到的 Promise resolve 办法,将参数转为 Promise 实例,再进一步解决。(Promise.all 办法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每个成员都是 Promise 实例。)
5. 介绍下 Promise.all, Promise.race() 的区别
看下面的介绍
Promise 的各种 api 实现会在下一篇文章中实现。给个期待吧😂
五、参考
Promise 必知必会(十道题)
BAT 前端经典面试问题:史上最最最具体的手写 Promise 教程
MDN async/await
六、完结
感激浏览到这里,如果着篇文章能对你有一点启发或帮忙的话欢送 github star, 我是林一一, 下次见。