逐步加深的异步操作(上)

栏目: IT资讯 · 发布时间: 6年前

内容简介:最近呢?第一主要是加班比较严重,还被高中同桌嘲讽:屌丝程序员,还讽刺我头发掉的快等等;第二呢 我最近和大学室友准备开一个开源项目,最近正在疯狂的筹划中,你能想象 设计:自己人、后台:自己人、前端:自己人、ui没有的痛苦吗?但是我们还是想做一个出来。哈哈,反正我们这种屌丝的想法不要钱。:joy::joy::joy:    好了,不哔哔了,这篇文章主要是来研究异步操作的。如果做过Android端的都知道,Android端有rxjava、多线程、aysnctask等好多东西来处理同步和异步的问题,但是初涉前端,很

最近呢?第一主要是加班比较严重,还被高中同桌嘲讽:屌丝程序员,还讽刺我头发掉的快等等;第二呢 我最近和大学室友准备开一个开源项目,最近正在疯狂的筹划中,你能想象 设计:自己人、后台:自己人、前端:自己人、ui没有的痛苦吗?但是我们还是想做一个出来。哈哈,反正我们这种屌丝的想法不要钱。:joy::joy::joy:    好了,不哔哔了,这篇文章主要是来研究异步操作的。如果做过Android端的都知道,Android端有rxjava、多线程、aysnctask等好多东西来处理同步和异步的问题,但是初涉前端,很多东西都是朦胧的,我就由浅及深谈一谈我所理解的异步操作。

##说在前头: 同步和异步呢?是 程序员 难以理解的一个东西,其实我对同步、异步也是略窥门径。我就简单说说我了解的同步、异步。 说起同步、我在这里把同步异步来做一个小例子帮助大家理解: 背景:小明暗恋着小红 同步: 就好比:小明约小红去吃饭,小明必须得到小红的响应才能做下一步动作,不然小明会在处于等待状态。 异步: 就好比:小红约小明去吃饭。小红对着空气喊了一声“小明,我去吃饭了”,然后小红就扬长而去了,不需要得到小明的回应。 总的来说,同步异步就只需要知道四个概念:

  • 同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。=
  • 异步 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。=
  • 阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
  • 非阻塞 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

一、黄阶下级斗技:胡搞一通,能运行就好了

我们刚开始入门的时候,经常会用一种完成任务的心态去实现功能。就例如现在页面启动的时候有N个请求,这就很有可能会写成这个样子:

{
	function get1(){
		console.log("get1...")
		setTimeout(function(){
			post1()
		},2000)
	}
	function get2(){
		console.log("get2...")
		setTimeout(function(){
			post2()
		},2000)
	}
	function post1(){
		console.log("post1...")
		setTimeout(function(){
			get2()
		},2000)
	}
	function post2(){
		console.log("post2...")
		setTimeout(function(){
			console.log("render...")
		},2000)
	}
	get1()
}
复制代码

运行结果如下

逐步加深的异步操作(上)

当然,还有更恐怖堆积在一个函数里面。这样的写法的好处就是所有的东西都处于一根线上面,便于大家理解。但是我们设身处地的想一下:如果你是用户,打开网站到看到视图需要这么长的白屏时间,这种体验就大打折扣。此时就需要异步操作了。 看下面一段代码

{
	function get1(){
		console.log("get1...")
		setTimeout(function(){
			console.log("get1 end...")
		},1000)
	}
	function get2(){
		console.log("get2...")
		setTimeout(function(){
			console.log("get2 end...")
		},1000)
	}
	function post1(){
		console.log("post1...")
		setTimeout(function(){
			console.log("post1 end...")
		},1000)
	}
	function post2(){
		console.log("post2...")
		setTimeout(function(){
			console.log("post2 end and render...")
		},1000)
	}
	get1()
	get2()
	post1()
	post2()
}
复制代码

运行结果如下:

逐步加深的异步操作(上)

如果按照我们通常的想法应该是get1 end之后才开始get2 end。但是上面的结果却大相径庭。查阅的一些官方文档,我也差不多略窥门径了,首先在js中存在一个执行队列和一个异步队列,如果存在有耗时任务,就会将该任务直接丢到异步队列,然后继续运行执行队形队列的任务。这就是js自己实现的一个异步操作。

那既然js内部自己就已经开始在使用异步了,我们为什么不好好来研究一波异步操作呢?

##二、 黄阶上级斗技:回调 如果你和我一样,都有一点java/android的底子,就一定会对回调有某种执念,同样js内部也可以完成这种写法,具体代码如下:

{
	function get1(...callback){
		console.log("get1...")
		setTimeout(function(){
			console.log("get1 end...")
			for(let index of callback){
				index()
			}
		},1000)
		console.log("do something...")
	}
	function get2(){
		console.log("get2...")
		setTimeout(function(){
			console.log("get2 end...")
		},1000)
	}
	function post1(){
		console.log("post1...")
		setTimeout(function(){
			console.log("post1 end...")
		},1000)
	}
	function post2(){
		console.log("post2...")
		setTimeout(function(){
			console.log("post2 end and render...")
		},1000)
	}
	get1(get2,post1,post2)
}
复制代码

运行结果如下:

逐步加深的异步操作(上)
这种写法看了大概就能明白回调的用法了。但是回调是什么? 百科是这样解释的: 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

在js中,我看到一种解释是这样说的:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

当然回调不一定只用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

{
	function f1(callback){
		console.log("do something=======>");
		// do somethings
		(callback && typeof(callback) === "function")&& callback();
	}

	function f2(){
		console.log("do others things======>");
	}
	f1(f2)
}
复制代码

关于回调呢?就算这么多,具体的思想前面也说了,es5以前回调是处理异步的主要方式。当然现在都2018年,es也出了很多的新方法,我来首先介绍一个promise。 ##三、 玄阶中级斗技:promise 说起promise这个东西,其实我在刚开始接触到前端的时候就有用到,主要是用来异步操作网络请求。首先介绍的是promise的两个特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

pomise内存实现了两个方法:resolve、reject。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

{
	//模拟一个网络请求
	let promise = new Promise((res,rej)=>{
		console.log("promise start====>")
		let data = Math.random()*10

		setTimeout(()=>{
			if(data>2){
				res("promise end sussess!!")
			}else{
				rej("promise end fail!!")
			}
		
		},3000)
	})
	promise.then(res=>{
		console.log(res)
	},res=>{
		console.log(res)
	})
}
复制代码

效果图如下:

逐步加深的异步操作(上)
逐步加深的异步操作(上)
由上面的效果我们可以明明白白的看出来promise新建之后就会立即执行。好的,我们这里开始解释前面的说法。promise在 new 出来之后就开始执行,里面会收到两个回调-- resolvereject 。我们这里把知道触发这两个回调的状态叫做 pending

,一旦触发两个回调之后就会立刻就会凝固。

{
	//模拟一个网络请求
	let promise = new Promise((res,rej)=>{
		console.log("promise start====>");
		throw new Error("test error")
	})
	promise.then(res=>{
		console.log(res)
	},res=>{
		console.log(res.message)
	})
}
复制代码
逐步加深的异步操作(上)
现在我们在模拟网络请求中出现错误的情况,如上可知 reject 本身就是可以拦截错误的。如果在promise中出错之后会走 reject

回调。但是我在看很多人的源码,很多大佬是这么写的:

{
	let promise = new Promise((res,rej)=>{
		console.log("promise start====>");
		throw new Error("test error")
		res("promise end sussess!!")
	})
	promise.then(res=>{
		console.log(res)
	}).catch(res=>{
		console.log(res.message)
	})
}
复制代码

刚开始我没有想通,但是当我写了很多东西之后就开始逐渐明白了。如下面的例子

{
	let promise = new Promise((res,rej)=>{
		console.log("promise start====>");
		res("promise end sussess!!")
	})
	promise.then(res=>{
		console.log(res)
		throw new Error("i meet some bug = =.");
	},res=>{
		console.log(res)
	})
}
复制代码

效果图如下:

逐步加深的异步操作(上)
我们发现reject并不能捕获 then

里面的错误,此时就用到大神们推荐的方式了。

{
	//模拟一个网络请求
	let promise = new Promise((res,rej)=>{
		console.log("promise start====>");
		res("promise end sussess!!")
	})
	promise.then(res=>{
		console.log(res)
		throw new Error("i meet some bug = =.");
	}).catch(res=>{
		console.log(res.message)
	})
}
复制代码

效果图如下:

逐步加深的异步操作(上)
并且我了解到源码中看到 catch

的实现方式就是:

then(null, rejection)    <===> catch
复制代码

我从进一步了解源码到:无论是 resolve ,和是 reject 都会返回一个 promise ,所以现在的代码就可以这么来写。

{
{
   let promise = new Promise((res,rej)=>{
   	console.log("promise start====>");
   	res("promise end sussess!!")
   })
   promise.then(res=>{
   	console.log(res)
   }).then(res=>{
   	console.log(res)
   	return "next next promise!!"
   }).then(res=>{
   	console.log(res);
   	throw new Error("i meet some bug = =.");
   }).catch(res=>{
   	console.log(res.message)
   }).then(res=>{
   	throw new Error("ai ya = =.");
   })
}
复制代码

效果图如下:

逐步加深的异步操作(上)

这里值得注意的两点:

  • 如果没有 return 下面的 then 是会返回 undefined ,相当于系统默认reject(undefined)
  • catch 并不能捕获到之后的error,如果想捕捉,就得继续去捕获了。

说到这里了,我就再插一句:我在最新的es2018中看到了finally方法,类比于 java 中的try,catch, finally,如果有兴趣,可以去看看最新的es2018的规则。我在这里就不做赘述了。我在这里最后来介绍promise在我的开发途径中常用的几个方法:

  • Promise.all() 使用方法:
{
	let p1 = new Promise((res,rej)=>{
		res('success!!');
	});

	let p2 = Promise.resolve("dosomething");


	Promise.all([p2,p1]).then(res=>{
		console.log(res);
	}).catch(res=>{
		console.log(res)
	})

}
复制代码

效果图:

逐步加深的异步操作(上)
上面值得注意的是:执行顺序是按照 all

方面里面的数组顺序执行的,这就能为我们封装网络请求做一定的便利。

{
	let p1 = new Promise((res,rej)=>{
		res('success!!');
	});

	let p2 = Promise.resolve("dosomething");

	let p3 = Promise.reject("i have bug ...")

	Promise.all([p1,p2,p3]).then(res=>{
		console.log(res);
	}).catch(res=>{
		console.log(res)
	})

}
复制代码

如果all之中存在抛出错误,其效果图如下

逐步加深的异步操作(上)
如果存在错误直接走 catch 方法. 我在这里做一下总结: ** Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject

失败状态的值。**

  • Promise.race() 使用方法:
{
	let p1 = new Promise((res,rej)=>{
		res('success!!');
	});

	let p2 = Promise.resolve("dosomething");

	let p3 = Promise.reject("i have bug ...")


	Promise.race([p1,p2,p3]).then(res=>{
		console.log(res);
	}).catch(res=>{
		console.log(res)
	})

}
复制代码

效果图如下:

逐步加深的异步操作(上)
race 相较于 all

方法就是,里面promise谁先处于完成状态就会走哪个方法。

说在最后

逐步加深的异步操作(上)
我看了时间点又看了看我的计划,看来我的焚决今晚是写不完了。先就这样吧。因为考虑到 Generator 这个到使用都花了近三天的时间,我来讲这个不知道一个小节能不能讲的通,更何况还有 decorator

这种bug呢?。

写文章第一:是为了梳理自己的知识体系;第二:写给读者。如果我写的东西,让一个新人都能看懂,这就说明我也达到了梳理知识体系的效果,所以我决定将异步操作分成几块来讲解,争取能让所有看文章的人都明白我写的东西。

12点了,不写了 不写了,好了,洗澡去了...最后说一句 小米涨了 ahhhhhhhhhh


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Computation and Programming Using Python

Introduction to Computation and Programming Using Python

John V. Guttag / The MIT Press / 2013-7 / USD 25.00

This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具