《你不知道的JavaScript》阅读笔记(6)

140 阅读6分钟

《你不知道的JavaScript》阅读笔记(6)

一、异步

1、异步的出现最大原因就是不阻塞用户的交互而产生

二、异步控制台

console.*并不是javascript正式的一部分,是由宿主环境添加到js中的(针对浏览器而言),有时候一个简单的console可能会让人产生混淆.

var a={
	index:1
}
console.log(a)
a.index++;

绝大多数情况下下面代码会按照你的想法来执行,但在某些情况下,浏览器会认为需要把控制台IO延迟到后台,在这种情况下a就会输出{index:2},具体什么情况可能只有你使用过程中会发现,如果某一次你发现了这种情况,不用感到困惑,你可以选择使用打断点的方式来进行调试。

三、事件循环

由于JS是单线程,所以需要提供一个一种机制来执行程序内多个代码块的执行,执行每一块是都是调用javascript引擎,比如我们发送一个请求,会写一个回调函数,让请求执行完后在执行这个回调函数,这个时候其实就是javascript引擎会通知宿主环境,跟他说拿到请求结果后把回调函数插入到事件循环中,等JS引擎有空的时候就会来执行这个回调函数

事件循环会把回调函数一个一个插入到一个队列中,等到引擎空闲时,会从队列中拿这些方法来执行,setTimeout的作用就是在多少秒后将某个函数加入到事件循环的队列中,所以setTimeout的精准度其实并不是很高,取决于当前任务队列的情况

四、回调

在Promise出现之前,回调一直是处理异步的主要方法,最典型的就是ajax执行完后的回调

ajax('<http://123.com>',function(data){
	console.log(data)
})

回调用的多了就会出现经典的回调地狱,所以这个时候Promise出现了

五、Promise

什么是Promise,Promise顾名思义是承诺的意思,就好比你在肯德基点单,点完单后不会马上给你送餐,而是会给你一个订单号,相当于给了你一个承诺,等到叫到你的时候你去取餐,当然叫到你之后可能会有多种结果,比如顺利取餐,或者是缺少了什么,再或者就是一直没有叫到你的号,上述事件可以说是Promise的一个很好的比喻.

我们来看一个例子

const x,y=2;
console.log(x+y);//很明显会输出nan因为x是不确定的

如果x和y是异步获取的值的这个,他在未来某个时候能获取到值这个时候我们该怎么获取总和呢,我们可以使用回调函数来解决这个问题

function getSum(getX,getY,cb){
	let x,y
	getX(a=>{
		x=a;
		if(!y){
				cb(x+y)
			}
	})
	getY(a=>{
		y=a;
		if(!x){
				cb(x+y)
			}
	})
}
getSum(fetchX,fetchY,console.log)

很明显,回调虽然能实现但是实现的很丑陋,下面我们看下Promise的实现方式

function getSum(XPromise,YPromise){
		Promise.all(XPromise,YPromise)
		.then(res=>{
				return res[0]+res[1]
		})
}
getSum(fetchX,fetchY).then(res=>{
	console.log(res)
})

首先getSum接收了两个Promise,这两个Promise分别承诺了在未来某个时候可以获取到X,Y,而Promise.all()则是当X,Y同时获取到值之后就可以拿到最后的结果,也就是x、y的值,.then就是未来我们拿到值时候的回调,它接收两个参数成功的回调,失败的回调,上面代码可以写成如下

function getSum(XPromise,YPromise){
		Promise.all(XPromise,YPromise)
		.then(res=>{
				return res[0]+res[1]
		},error=>{
				console.log(error)//如果获取值的过程中产生了某种异常则会执行
		})
}

Promise的状态一旦被确定了,就是不会改变的

Promise对于流程控制是非常有帮助的,以往回调是事件内部执行完之后自动执行我们的回调,而Promise则是给了我们主动权,它可以作为一个三方的协商机制,它可以让一个异步任务得到独立的通知。

如下

function foo(x)=>new Promise((resolve,reject)=>resolve(x))
const a=foo(3)
const b=foo(4)
listener(a)
listener(b)

上面代码中的resolve和reject是Promise状态的决议方法,resolve可以理解为成功,reject则为失败,listener则是分别监听我们a,b两个Promise内部实现则有他们自己控制

Promise调用then方法的时候肯定是异步的且一定是下一个异步调度的结点

向Promise.resolve传入一个非Promise、非thenable的立即值,就会得到用这个值填充的Promise,如下,这两个其实表达的意思是一样的

const p1=Promise.resolve(function(resolve){
	resolve(43)
})
const p2=Promise.resolve(43)

当然Promise.resolve也可以接受传入一个Promise,他只会返回一个Promise

const p1=Promise.resolve(1)
const p2=Promise.resolve(p1)
p1===p2//true

Promise的链式流:

1、每次对调用Promise.then的时候就会返回一个新的Promise

2、不管then中的第一个回调返回的是什么,他都会自动设置为被链接Promise的完成

第二句可能比较难理解,先来看下链式调用

const p=Promise.resolve(3)
p.then(res=>{
	return res*2
}).then(res=>{
	console.log(res)//6
})

我们可以换一种方式来理解

const p1=Promise.resolve(32)
p1.then(res=>new Promise((r,j)=>{
	setTimeout(()=>{
		r(res)
	},3000)
})).then(res=>{
	//隔三秒后输出
	console.log(32)
})

我们p1 then回调返回了一个Promise,我们产生的新的Promise就会执行Promise.resolve(new Promise.....)类似这样一句,所以我们就会得到一个传入的Promise

考虑捕捉链式调用的Promise某个出错可以如下

const p1=Promise.resolve(...)
p1.then(res=>{
	return res2
}).then(res=>{
	return res3
},(err)=>{
	return err
}).then(res=>{
	console.log(res)//err
})

上述如果在最后一个then链里面出错了那么我们该怎么捕获错误呢,一般而会在最后在加一个catch,因为错误也会被传递到最后一个Promise中

p1.then(...)
.then(...)
.then(...)
.catch(...)

那如果最后的catch里也会有问题该怎么办呢,这时候其实就需要一个全局的错误捕获来抛出这个问题,现代浏览器一般是这样做的:它可以跟踪对象被丢弃以及被回收的时机,如果在垃圾回收的时候发现有拒绝,则就可以确定这是一个未被追踪的错误,就会在终端中抛出这个错误

Promise.all:返回一个Promise,传参数可以传入一个普通值,Promise,或者thenable的对象,它内部都会默认调用一次Promise.resolve确保需要等待的为Promise,会在所有Promise完成后才会返回所有结果,一旦某个Promise出错了,返回的Promise则会立刻被拒绝,并且丢弃所有结果。

Promise.race:返回和传参与Promise.all类似,但它只会返回第一个完成的Promise的值,不管这个Promise是被完成了还是被拒绝了

Promise.all如果传入空数组则会立即执行完成,而Promise.race传入空数组会被挂起,永不决议

JS中的Promise是不允许中断的