一、单线程和异步
1、单线程是什么
(1) JS
是单线程语言,只能同时做一件事情
- 所谓单线程,就是只能同时做一件事情,多一件都不行,这就是单线程。
(2) 浏览器和 nodejs
已支持 JS
启动进程,如 Web Worker
(3)JS
和 DOM
渲染共用同一个线程,因为 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
主要有以上三种状态, pending
、 fulfilled
和 rejected
。当返回一个 pending
状态的 promise
时,不会触发 then
和 catch
。当返回一个 fulfilled
状态时,会触发 then
回调函数。当返回一个 rejected
状态时,会触发 catch
回调函数。那在这几个状态之间,他们是怎么变化的呢?
1)演示1
先来看一段代码。
const p1 = new Promise((resolved, rejected) => { }); console.log('p1', p1); //pending 复制代码
在以上的这段代码中,控制台打印结果如下。
在这段代码中, p1
函数里面没有内容可以执行,所以一直在等待状态,因此是 pending
。
2)演示2
const p2 = new Promise((resolved, rejected) => { setTimeout(() => { resolved(); }); }); console.log('p2', p2); //pending 一开始打印时 setTimeout(() => console.log('p2-setTimeout', p2)); //fulfilled 复制代码
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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
在以上的这段代码中,控制台打印结果如下。
在这段代码中, p3
一开始打印的是 pending
状态,因为它没有执行到 setTimeout
里面。等到后续执行 setTimeout
时,同样地,会触发到 rejected
函数,触发后返回一个 rejected
状态的 promise
。
看完 promise
状态的变化后,相信大家对 promise
的三种状态分别在什么时候触发会有一定的了解。那么我们接下来继续看 promise
状态的表现。
(2)状态的表现
pending
状态,不会触发then
和catch
。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); }); 复制代码
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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); })
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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'); });
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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); });
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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); });
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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'); });
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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); });
这道题打印的是 1
和 3
,因为调用的是 promise
的 resolve
函数,所以后续不会触发 .catch
函数。
第二题:
Promise.resolve().then(() => { console.log(1); throw new Error('error'); }).catch(() => { console.log(2); }).then(() => { console.log(3); });
这道题打印的是 1
和 2
,虽然调用的是 promise
的 resolve
函数,但是中间抛出了一个异常,所以此时 promise
变为 rejected
状态,所以后续不会触发 .then
函数。
第三题:
Promise.resolve().then(() => { console.log(1); throw new Error('error'); }).catch(() => { console.log(2); }).catch(() => { //这里是catch console.log(3); });
这道题打印的是 1
和 2
和 3
,跟第二题一样,中间抛出了一个异常,所以此时 promise
变为 rejected
状态,所以后续只触发 .catch
函数。