前言
Promise
Promise 是 JavaScript 中用于处理异步操作的一种编程模型,它代表了未来可能得到的一个结果(可能是成功的数据或失败的原因)。Promise 的主要目的是为了解决回调地狱(callback hell)问题,使异步代码更加易于理解和维护。
在详细学习Promise作用之前先简单了解一下 JavaScript 引擎处理异步操作机制
Event Loop
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
Event Loop
Event Loop 是 JavaScript 引擎处理异步操作的一种机制,它允许 JavaScript 运行时环境(如浏览器和 Node.js)实现非阻塞的输入输出(I/O)和其他异步操作,同时保持单线程执行模型。以下是 Event Loop 的核心概念和工作流程:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
- 单线程执行:JavaScript 采用单线程执行模型,意味着同一时间只能执行一个任务。这是因为早期 JavaScript 主要用于浏览器脚本,为了避免多线程带来的复杂性(如竞态条件、死锁等),选择了单线程。
- 任务队列:Event Loop 将任务分为两大类:
- 宏任务(Macrotasks) :包括 script(整体代码)、setTimeout、setInterval、I/O、UI渲染等。
- 微任务(Microtasks) :包括
Promis
、MutationObserver 等。
- 执行流程:
1、 执行全局脚本代码:首先执行当前执行环境下的脚本代码,这些代码作为第一个宏任务执行。 2、 检查微任务队列:执行完当前宏任务后,Event Loop 会查看是否有待处理的微任务。如果有,则按照先进先出的顺序执行所有微任务,直到微任务队列为空。 3、 渲染界面(可选步骤) :在某些环境中(如浏览器),在每个宏任务结束且微任务执行完毕后,有机会进行界面渲染。 4、 检查宏任务队列:接下来,Event Loop 会检查宏任务队列,取出队列中的下一个任务执行,重复上述过程。
- 循环迭代:这个过程会不断重复,形成一个循环,这就是“Event Loop”名称的由来。每次循环称为一个“tick”。
执行顺序为:start
-> end
-> Promise
-> setTimeout
。即使 setTimeout
设置了0延迟,它也是在当前宏任务结束后,且微任务队列清空之后才执行。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
Event Loop 机制确保了 JavaScript 能够高效地处理并发操作,同时保持代码的可预测性和简单性。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
正文
我们先来回忆一下异步执行
如下是异步编程和回调函数的使用文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
js
var data = null
function a(cb) {
setTimeout(function () {
data = 'data';
cb()
}, 1000);
}
function b(){
console.log(data);
}
a(b)
这段代码 setTimeout
创建异步操作,并通过回调函数(cb
)在异步操作完成后再执行其他逻辑文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
其执行流程为:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
- 首先,执行到
a(b)
,此时data
仍然是null
。 setTimeout
开始计时,但因为是异步操作,程序不会等待这个延时结束,而是继续执行后续代码(如果有的话)。- 1秒后,
setTimeout
回调执行,data
的值被改为'data'
,然后调用b()
接着来感受一下回调地狱吧
js
function a(cbB,cbC,cbD){
cbB(cbC,cbD)
}
function b(cb,cbD){
cb(cbD)
}
function c(cb){
cb()
}
function d(){
}
a(b,c,d)
这段代码定义了一个函数 a
,它接受三个参数,分别是三个函数 cbB
、cbC
和 cbD
。函数 a
的体内调用了 cbB
函数,并将 cbC
和 cbD
作为参数传递给 cbB
。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
又定义了一个函数 b
,它接受两个参数,分别是两个函数 cb
和 cbD
。函数 b
的体内调用了 cb
函数,并将 cbD
作为参数传递给 cb
。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
还定义了一个函数 c
,它接受一个函数 cb
作为参数,并在函数体内部调用 cb
函数。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html
并且定义了一个无参数的函数 d
。
- 函数调用:
a(b, c, d)
:调用函数a
,并将函数b
、c
、d
分别作为参数传递给它。这意味着:cbB
被赋值为b
,cbC
被赋值为c
,cbD
被赋值为d
。
根据
a
函数的定义,它会执行cbB(cbC, cbD)
,即调用b(c, d)
。
- 函数
b
的执行:- 当
b
被调用时,它接收到c
作为cb
和d
作为cbD
。 b
函数内部调用cb(cbD)
,即调用c(d)
。
- 当
- 函数
c
的执行:c
接收d
作为参数,并在函数体内部调用cb()
,即调用d()
。
看到这四个函数调来调去的是不是感觉头晕目眩的呢?做这种项目的时候感觉来到了地狱
Promise
举个生活中的例子:起床,刷牙,吃早饭
使用情景为,在同步和异步都存在的情况下,想要函数按指定顺序输出结果
js
function getup() {
setTimeout(()=> {
console.log('起床');
},1000)
})
}
function wish() {
console.log('刷牙');
}
getup()
wish()
这样输出的结果为:刷牙,起床,因为JS引擎先执行同步再执行异步
为了让起床输出位置在刷牙之前,可以使用Promise模型处理异步操作
js
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
},2000)
})
}
function wish() {
console.log('刷牙');
}
getup().then(()=>{
wish();
})
.then
是 Promise 对象上的一个方法,用于指定在 Promise 完成(即变为 fulfilled 状态)时应该执行的函数,也就是当异步操作成功完成时的回调。
通过Promise和.then能够使得先执行完getup()再执行wish(),不过以上代码无法输出wish(),因为目前getup()还处于Promise的默认状态pending(等待中)
Promise 有三种状态:
- pending(等待中):初始状态,既没有成功也没有失败。
- fulfilled(已成功):表示操作成功完成。
- rejected(已失败):表示操作失败。
resolve()
函数是用来改变 Promise 的状态,从 "pending"(等待中)变为 "fulfilled"(已成功),表示异步操作已经成功完成。
js
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
resolve()
},2000)
})
}
function wish() {
console.log('刷牙');
}
getup().then(()=>{
wish();
})
接下来再和上述过程一样的添加eat()函数即可
嵌套多层.then
可以实现输出异步操作的值,但在大型项目中采用该方法
js
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
resolve()
},2000)
})
}
function wish() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('刷牙');
resolve()
},2000)
})
}
function eat(){
console.log('吃早饭');
}
getup().then(()=>{
wish().then(()=>{
eat()
})
})
.then
方法也可以链式调用,使得处理异步操作变得更加流畅和易于阅读。
但需要注意,要让链式调用不出错误,必须返回调用的函数(即return)
js
getup()
.then(() =>{
return wish()
})
.then(() =>{
eat()
})
除此之外.then
它有两个参数:第一个参数是处理成功情况(resolve)的回调函数,第二个参数(可选)是处理失败情况(reject)的回调函数。
当你调用 resolve()
函数时,你可以传递一个参数,这个参数通常代表异步操作的结果,它会被传递给 .then
方法中注册的回调函数。
js
function a(){
return new Promise((resolve,reject)=>{
setTimeout(function() {
console.log('a is ok');
data = 'a'
resolve('gagaggag')
reject('no')
},2000)
})
}
function b(){
console.log(data);
}
// a().then((b))
a().then((res) => {
console.log(res);// gagaggag
b() // a
})
.catch((err) => {
console.log(err); // 只有在发生错误的时候才会执行 no
})
如果异步操作(如数据获取)失败,reject('no')
会被调用,错误信息 'no'
会被传递给 .catch
方法处理,从而可以在控制台看到相应的错误消息。
其中
reject
状态只有在发生错误的时候才会执行.catch
可以有效避免程序直接报错,而导致系统崩溃
总结
Promise
Promise
是 JavaScript 中用于处理异步操作的一种编程模型,它代表了未来可能得到的一个结果(可能是成功的数据或失败的原因)。Promise 的主要目的是为了解决回调地狱
(callback hell)问题,使异步代码更加易于理解和维护。
.then
是Promise
原型上的一个函数,x.then()
then函数会在x这个promise实例对象状态变更为 resolved
之后才能执行内部逻辑,由此借助这个机制可以将异步捋为同步
then
方法支持链式调用,因为then默认也会返回一个promise实例对象,但是状态默认为pending
,这就导致后面的then用不上前面then的状态,从而继续向前查找- 我们在
then
中返回一个promise
实例对象,那么这个promise
实例对象就会作为then的返回值,将覆盖then自带的返回值 reslove(x)
这个x会指定交给then
中的回调函数reject(x)
这个x会指定交给catch
中的回调函数,catch
是专门用来捕获程序中的错误的,而不会让程序崩溃
评论