解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!(一)

简介: 解决异步问题——promise、async/await

一、单线程和异步



1、单线程是什么


(1)  JS 是单线程语言,只能同时做一件事情

  • 所谓单线程,就是只能同时做一件事情,多一件都不行,这就是单线程。

(2) 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker

(3)JSDOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构

  • JS 可以修改 DOM 结构,使得它们必须共用同一个线程,这间接算是一件迫不得已的事情。
  • 所以当 DOM 在渲染时 JS 必须停止执行,而 JS 执行过程 DOM 渲染也必须停止。


2、为什么需要异步


当程序遇到网络请求或定时任务等问题时,这个时候会有一个等待时间。

假设一个定时器设置10s,如果放在同步任务里,同步任务会阻塞代码执行,我们会等待10s后才能看到我们想要的结果。1个定时器的等待时间可能还好,如果这个时候是100个定时器呢?我们总不能等待着1000s的时间就为了看到我们想要的结果吧,这几乎不太现实。

那么这个时候就需要异步,通过异步来让程序不阻塞代码执行,灵活执行程序。


3、使用异步的场景


(1)异步的请求,如ajax图片加载

//ajax
console.log('start');
$.get('./data1.json', function (data1) {
    console.log(data1);
});
console.log('end');

(2)定时任务,如setTimeout、setInterval

//setTimeout
console.log(100);
setTimeout(fucntion(){
  console.log(200);           
}, 1000);
console.log(300);
//setInterval
console.log(100);
setInterval(fucntion(){
  console.log(200);           
}, 1000);
console.log(300);


二、promise


早期我们在解决异步问题的时候,基本上都是使用callback回调函数的形式 来调用的。形式如下:

//获取第一份数据
$.get(url1, (data1) => {
    console.log(data1);
    //获取第二份数据
    $.get(url2, (data2) => {
        console.log(data2);
        //获取第三份数据
        $.get(url3, (data3) => {
            console.log(data3);
            //还可以获取更多数据
        });
    });
});

从上述代码中可以看到,早期在调用数据的时候,都是一层套一层, callback 调用 callback ,仿佛深陷调用地狱一样,数据也被调用的非常乱七八糟的。所以,因为 callback 对开发如此不友好,也就有了后来的 promise 产生, promise 的出现解决了 callback hell 的问题。

用一段代码先来了解一下 Promise

function getData(url){
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            success(data){
                resolve(data);
            },
            error(err){
        reject(err);
            }
        });
    });
}
const url1 = '/data1.json';
const url2 = '/data2.json';
const url3 = './data3.json';
getData(url1).then(data1 => {
    console.log(data1);
    return getData(url2);
}).then(data2 => {
    console.log(data2);
    return getData(url3);
}).then(data3 => {
    console.log(data3);
}).catch(err => console.error(err));

大家可以看到,运用了 promise 之后,代码不再是一层套一层,而是通过 .then 的方式来对数据进行一个获取,这在写法上就已经美观了不少。那 promise 究竟是什么呢?接下来开始进行讲解。


1、promise的三种状态


  • pending :等待状态,即在过程中,还没有结果。比如正在网络请求,或定时器没有到时间。
  • fulfilled :满足状态,即事件已经解决了,并且成功了;当我们主动回调了 fulfilled 时,就处于该状态,并且会回调 then 函数。
  • rejected :拒绝状态,即事件已经被拒绝了,也就是失败了;当我们主动回调了 reject 时,就处于该状态,并且会回调 catch 函数。

总结:

  • 只会出现pending → fulfilled,或者pending → rejected 状态,即要么成功要么失败;
  • 不管是成功的状态还是失败的状态,结果都不可逆。


2、三种状态的表现和变化


(1)状态的变化


promise 主要有以上三种状态, pendingfulfilledrejected 。当返回一个 pending 状态的 promise 时,不会触发 thencatch 。当返回一个 fulfilled 状态时,会触发 then 回调函数。当返回一个 rejected 状态时,会触发 catch 回调函数。那在这几个状态之间,他们是怎么变化的呢?

1)演示1

先来看一段代码。

const p1 = new Promise((resolved, rejected) => {
});
console.log('p1', p1); //pending
复制代码

在以上的这段代码中,控制台打印结果如下。

60.png

在这段代码中, p1 函数里面没有内容可以执行,所以一直在等待状态,因此是 pending

2)演示2

const p2 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        resolved();
    });
});
console.log('p2', p2); //pending 一开始打印时
setTimeout(() => console.log('p2-setTimeout', p2)); //fulfilled
复制代码

在以上的这段代码中,控制台打印结果如下。

61.png

在这段代码中, p2 一开始打印的是 pending 状态,因为它没有执行到 setTimeout 里面。等到后续执行 setTimeout 时,才会触发到 resolved 函数,触发后返回一个 fulfilled 状态 promise

3)演示3

const p3 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        rejected();
    });
});
console.log('p3', p3);
setTimeout(() => console.log('p3-setTimeout', p3)); //rejected

在以上的这段代码中,控制台打印结果如下。

62.png

在这段代码中, p3 一开始打印的是 pending 状态,因为它没有执行到 setTimeout 里面。等到后续执行 setTimeout 时,同样地,会触发到 rejected 函数,触发后返回一个 rejected 状态的 promise

看完 promise 状态的变化后,相信大家对 promise 的三种状态分别在什么时候触发会有一定的了解。那么我们接下来继续看 promise 状态的表现。


(2)状态的表现


  • pending 状态,不会触发 thencatch
  • fulfilled 状态,会触发后续的 then 回调函数。
  • rejected 状态,会触发后续的 catch 回调函数。

我们来演示一下。

1)演示1

const p1 = Promise.resolve(100); //fulfilled
console.log('p1', p1);
p1.then(data => {
    console.log('data', data);
}).catch(err => {
    console.error('err', err);
});
复制代码

在以上的这段代码中,控制台打印结果如下。

63.png

在这段代码中, p1 调用 promise 中的 resolved 回调函数,此时执行时, p1 属于 fulfilled 状态, fulfilled 状态下,只会触发 .then 回调函数,不会触发 .catch ,所以最终打印出 data 100

2)演示2

const p2 = Promise.reject('404'); //rejected
console.log('p2', p2);
p2.then(data => {
    console.log('data2', data);
}).catch(err => {
    console.log('err2', err);
})

在以上的这段代码中,控制台打印结果如下。

64.png

在这段代码中, p2 调用 promise 中的 reject 回调函数,此时执行时, p1 属于 reject 状态, reject 状态下,只会触发 .catch 回调函数,不会触发 .then ,所以最终打印出 err2 404

对三种状态有了基础了解之后,我们来深入了解 .then.catch 对状态的影响。


3、then和catch对状态的影响(重要)


  • then 正常返回 fulfilled ,里面有报错则返回 rejected
  • catch 正常返回 fulfilled ,里面有报错则返回 rejected

我们先来看第一条规则:then 正常返回 fulfilled ,里面有报错则返回 rejected

1)演示1

const p1 = Promise.resolve().then(() => {
    return 100;
})
console.log('p1', p1); //fulfilled状态,会触发后续的.then回调
p1.then(() => {
    console.log('123');
});

在以上的这段代码中,控制台打印结果如下。

65.png

在这段代码中, p1 调用 promise 中的 resolve 回调函数,此时执行时, p1 正常返回 fulfilled , 不报错,所以最终打印出 123

2)演示2

const p2 = Promise.resolve().then(() => {
    throw new Error('then error');
});
console.log('p2', p2); //rejected状态,触发后续.catch回调
p2.then(() => {
    console.log('456');
}).catch(err => {
    console.error('err404', err);
});

在以上的这段代码中,控制台打印结果如下。

66.png

在这段代码中, p2 调用 promise 中的 resolve 回调函数,此时执行时, p2 在执行过程中,抛出了一个 Error ,所以,里面有报错则返回 rejected 状态 , 所以最终打印出 err404 Error: then error 的结果。

我们再来看第二条规则catch 正常返回 fulfilled ,里面有报错则返回 rejected

1)演示1(需特别谨慎! !)

const p3 = Promise.reject('my error').catch(err => {
    console.error(err);
});
console.log('p3', p3); //fulfilled状态,注意!触发后续.then回调
p3.then(() => {
    console.log(100);
});

在以上的这段代码中,控制台打印结果如下。

67.png

在这段代码中, p3 调用 promise 中的 rejected 回调函数,此时执行时, p3 在执行过程中,正常返回了一个 Error这个点需要特别谨慎!!这看起来似乎有点违背常理,但对于 promise 来说,不管时调用 resolved 还是 rejected ,只要是正常返回而没有抛出异常,都是返回 fulfilled 状态。所以,最终 p3 的状态是 fulfilled 状态,且因为是 fulfilled 状态,之后还可以继续调用 .then 函数。

2)演示2

const p4 = Promise.reject('my error').catch(err => {
    throw new Error('catch err');
});
console.log('p4', p4); //rejected状态,触发.catch回调函数
p4.then(() => {
    console.log(200);
}).catch(() => {
    console.log('some err');
});

在以上的这段代码中,控制台打印结果如下。

68.png

在这段代码中, p4 依然调用 promise 中的 reject 回调函数,此时执行时, p4 在执行过程中,抛出了一个 Error ,所以,里面有报错则返回 rejected 状态 , 此时 p4 的状态为 rejected ,之后触发后续的 .catch 回调函数。所以最终打印出 some err 的结果。


4、then和catch的链式调用(常考)


学习完以上知识后,我们通过几道题来再总结回顾一下。

第一题:

Promise.resolve().then(() => {
    console.log(1);
}).catch(() => {
    console.log(2);
}).then(() => {
    console.log(3);
});

这道题打印的是 13 ,因为调用的是 promiseresolve 函数,所以后续不会触发 .catch 函数。

第二题:

Promise.resolve().then(() => {
    console.log(1);
    throw new Error('error');
}).catch(() => {
    console.log(2);
}).then(() => {
    console.log(3);
});

这道题打印的是 12 ,虽然调用的是 promiseresolve 函数,但是中间抛出了一个异常,所以此时 promise 变为 rejected 状态,所以后续不会触发 .then 函数。

第三题:

Promise.resolve().then(() => {
    console.log(1);
    throw new Error('error');
}).catch(() => {
    console.log(2);
}).catch(() => {  //这里是catch
    console.log(3);
});

这道题打印的是 123 ,跟第二题一样,中间抛出了一个异常,所以此时 promise 变为 rejected 状态,所以后续只触发 .catch 函数。


相关文章
|
3月前
|
前端开发 JavaScript
async/await和Promise在性能上有什么区别?
性能优化是一个综合性的工作,除了考虑异步模式的选择外,还需要关注代码的优化、资源的合理利用等方面。
52 4
|
3月前
|
前端开发
如何使用async/await解决Promise的缺点?
总的来说,`async/await` 是对 Promise 的一种很好的补充和扩展,它为我们提供了更高效、更易读、更易维护的异步编程方式。通过合理地运用 `async/await`,我们可以更好地解决 Promise 的一些缺点,提升异步代码的质量和开发效率。
49 5
|
3月前
|
前端开发 数据处理
如何使用 Promise.all() 处理异步并发操作?
使用 `Promise.all()` 可以方便地处理多个异步并发操作,提高代码的执行效率和可读性,同时通过统一的 `.catch()` 方法能够有效地处理异步操作中的错误,确保程序的稳定性。
|
3月前
|
存储 前端开发
除了 Promise.all(),还有哪些方法可以处理异步并发操作?
在上述示例中,`concurrentPromises` 函数接受一个Promise数组和最大并发数作为参数,通过手动控制并发执行的Promise数量,实现了对异步操作的并发控制,并在所有Promise完成后返回结果数组。
|
3月前
|
前端开发 JavaScript
如何使用 Promise 处理异步并发操作?
通过使用 `Promise.all()` 和 `Promise.race()` 方法,可以灵活地处理各种异步并发操作,根据不同的业务需求选择合适的方法来提高代码的性能和效率,同时也使异步代码的逻辑更加清晰和易于维护。
|
3月前
|
前端开发 JavaScript 开发者
用 Promise 处理异步操作的优势是什么?
综上所述,使用Promise处理异步操作能够有效地解决传统回调函数带来的诸多问题,提高代码的质量、可读性、可维护性和可扩展性,是JavaScript中进行异步编程的重要工具和技术。
|
3月前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
3月前
|
前端开发 JavaScript 开发者
Async 和 Await 是基于 Promise 实现
【10月更文挑战第30天】Async和Await是基于Promise实现的语法糖,它们通过简洁的语法形式,借助Promise的异步处理机制,为JavaScript开发者提供了一种更优雅、更易于理解和维护的异步编程方式。
49 1
|
7月前
|
前端开发 JavaScript
JavaScript异步编程:Promise与async/await的深入探索
【7月更文挑战第9天】Promise和async/await是JavaScript中处理异步编程的两大利器。Promise为异步操作提供了统一的接口和链式调用的能力,而async/await则在此基础上进一步简化了异步代码的书写和阅读。掌握它们,将使我们能够更加高效地编写出清晰、健壮的异步JavaScript代码。
|
7月前
|
前端开发 JavaScript 定位技术
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
122 1