JS异步处理的进化史深入讲解

前言

javascript是一门单线程的语言,也就是说一次只能完成一件任务,如果有多个任务,就需要排队进行处理。如果一个任务耗时很长,后面的任务也必须排队等待,这样大大的影响了整个程序的执行。为了解决这个问题,javascript语言将任务分为两种模式:

  • 同步:当我们打开网站,网页的页面骨架渲染和页面元素渲染,就是一大推同步任务。
  • 异步:我们在浏览新闻时,加载图片或音乐之类占用资源大且耗时久的任务就是异步任务。

本文主要针对近两年javascript的发展,主要介绍异步处理的进化史。目前,在javascript异步处理中,有以下几种方式:

callback

回调函数是最早解决异步编程的方法。无论是常见的setTimeout还是ajax请求,都是采用回调的形式把事情在某一固定的时刻进行执行。

 //常见的:setTimeout
 setTimeout(function callback(){
  console.log('aa');
 }, 1000);
 //ajax请求
 ajax(url,function callback(){
  console.log("ajax success",res);
 })

回调函数的处理一般将函数callback作为参数传进函数,在合适的时候被调用执行。回调函数的优点就是简单、容易理解和实现,但有个致命的缺点,容易出现回调地狱(Callback hell),即多个回调函数嵌套使用。造成代码可读性差、可维护性差且只能在回调中处理异常。

ajax(url, () => {
	//todo
	ajax(url1, () => {
		//todo
		ajax(url2, () => {
			//todo
		})
	})
})

事件监听

事件监听采用的是事件驱动的模式。事件的执行不取决于代码的顺序,而是某个事件的发生。

假设有两个函数,为f1绑定一个事件(jQuery的写法),当f1函数发生success事件时,执行函数f2:

f1.on('success',f2);

对f1进行改写:

function f1(){
	ajax(url,() => {
		//todo
		f1.trigger('success');//触发success事件,从而执行f2函数
	})
}

事件监听的方式较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。

发布订阅

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做 发布/订阅模式(publish-subscribe pattern),又称**观察者模式"(observer pattern) **。

//利用jquery的插件实现
//首先,f2向消息中心订阅success事件
jQuery.subscribe('success',f2);
//对f1进行改写:
function f1(){
	ajax(url,() => {
		//todo
		jQuery.publish('success');//当f1执行完毕后,向消息中心jQuery发布success事件,从而执行f2函数
	})
}
//f2执行完毕后,可以取消订阅
jQuery.unsubscribe('success',f2)

该方法和事件监听的性质类似,但我们可以通过消息中心来查阅一共有多少个信号,每个信号有多少个订阅者。

Promise

**Promise**是CommonJS工作组提出的一种规范,可以获取异步操作的消息,也是异步处理中常用的一种解决方案。Promise的出现主要是用来解决回调地狱、支持多个并发的请求,获取并发请求的数据并且解决异步的问题。

let p = new Promise((resolve, reject) => {
 //做一些异步操作
 setTimeout(()=>{
  let num = parseInt(Math.random()*100);
  	if(num > 50){
   resolve("num > 50"); // 如果数字大于50就调用成功的函数,并且将状态变成Resolved
  	}else{
   	reject("num <50");// 否则就调用失败的函数,将状态变成Rejected
  }
	},10000)
});
p.then((res) => {
	console.log(res);
}).catch((err) =>{
 console.log(err);
})

Promise有三种状态:等待pending、成功fulfied、失败rejected;状态一旦改变,就不会再变化,在Promise对象创建后,会马上执行。等待状态可以变为fulfied状态并传递一个值给相应的状态处理方法,也可能变为失败状态rejected并传递失败信息。任一一种情况出现时,Promise对象的 then 方法就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,均为 Function。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法)。

需要注意的是: Promise.prototype.then 和  Promise.prototype.catch 方法返回promise 对象, 所以可以被链式调用,如下图:

Promise的方法:

  • Promise.all(iterable):谁执行得慢,以谁为准执行回调。返回一个promise对象,只有当iterable里面的所有promise对象成功后才会执行。一旦iterable里面有promise对象执行失败就触发该对象的失败。对象在触发成功后,会把一个包iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
  • Promise.race(iterable): 谁执行得快,以谁为准执行回调。iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
  • Promise.reject(err)与Promise.resolve(res)

Generators/yield

Generators是ES6提供的异步解决方案,其最大的特点就是可以控制函数的执行。可以理解成一个内部封装了很多状态的状态机,也是一个遍历器对象生成函数。Generator 函数的特征:

  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield表达式,定义不同的内部状态;
    • 通过yield暂停函数,next启动函数,每次返回的是yield表达式结果。next可以接受参数,从而实现在函数运行的不同阶段,可以从外部向内部注入不同的值。next返回一个包含value和done的对象,其中value表示迭代的值,后者表示迭代是否完成。

举个例子:

function* createIterator(x) {
 let y = yield (x+1)
 let z = 2*(yield(y/3))
 return (x+y+z)
}
// generators可以像正常函数一样被调用,不同的是会返回一个 iterator
let iterator = createIterator(4);
console.log(iterator.next()); // {value:5,done:false}
console.log(iterator.next()); // {value:NaN,done:false}
console.log(iterator.next()); // {value:NaN,done:true}
let iterator1 = createIterator(4);//返回一个iterator
//next传参数
console.log(iterator1.next());  // {value:5,done:false}
console.log(iterator1.next(12)); // {value:4,done:false}
console.log(iterator1.next(15)); // {value:46,done:true}

代码分析:

  • 当不参数时,next的value返回NaN;
  • 当传参数时,作为上一个yeild的值,在第一次使用next时,传参数无效,只有第二次开始,才有效。
  • 第一次执行next时,函数会被暂停在yeild(x+1),所以返回的是4+1=5;
  • 第二次执行next时,传入的12为上一次yeild表达式的值,所以y=12,返回的是12/3=4;
  • 第三次执行next时,传入的15为上一次yeild表达式的值,所以z=30,y=12;x=4,返回30+12+4=46

async/await

初入async/await

async/await在ES7提出,是目前在javascript异步处理的终极解决方案。

async 其本质是 Generator 函数的语法糖。相较于Generator放入改进如下:

  • 内置执行器:Generator 函数的执行必须靠执行器,而async函数自带执行器。其调用方式与普通函数一模一样,不需要调next方法;
  • 更好的语义:async表示定义异步函数,而await表示后面的表达式需要等待,相较于*和yeild更语义化;
  • 更广的适用性:co模块约定,yield命令后面只能是Thunk函数或 Promise对象。而 async 函数的await命令后面则可以是Promise 或者 原始类型的值;
  • 返回Promise:async 函数返回值是Promise对象,比 Generator函数返回的 Iterator对象方便,可以直接使用 then() 方法进行链式调用;

语法分析

async语法

用来定义异步函数,自动将函数转换为promise对象,可以使用then来添加回调,其内部return的值作为then回调的参数。

async function f(){
	return "hello async";
}
f().then((res) => {  //通过then来添加回调且内部返回的res作为回调的参数
	console.log(res);  // hello async
})

在异步函数的内部可以使用await,其返回的promise对象必须等到内部所以await命令后的promise对象执行完,才会发生状态变化即执行then回调。

const delay = function(timeout){
	return new Promise(function(resolve){
		return setTimeout(resolve, timeout);
 });
}
async function f(){
  await delay(1000);
  await delay(2000);
  return '完成';
}
f().then(res => console.log(res));//需要等待3秒之后才会打印:完成

await即表示异步等待,用来暂停异步函数的执行,只能在异步函数和promise使用,且当使用在promise前面,表示等待promise完成并返回结果。

async function f() {
  return await 1  //await后面不是Promise的话,也会被转换为一个立即为resolve的promise
};
f().then( res => console.log("处理成功",res))//打印出:处理成功 1
	 .catch(err => console.log("处理是被",err))////打印出:Promise{<resolved>:undefined}

错误处理

如果await后面的异步出现错误,等同于async返回的promise对象为reject,其错误会被catch的回调函数接收到。需要注意的是,当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。

let a;
async function f(){
  await Promise.reject("error")
  a = await 1    //该await并没有执行
}
err().then(res => console.log(a))

怎么处理呢,可以把第一个await放在try/catch,遇到函数的时候,可以将错误抛出并往下执行。

async function f() {
  try{
    await Promise.reject('error');
  }catch(error){
    console.log(error);
  }
  return await 1
}
f().then(res => console.log('成功', res))//成功打印出1

如果有多个await处理,可以统一放在try/catch模块中,而且async可以使得try/catch同时处理同步和异步错误。

总结

通过以上六种javascript异步处理的常用方法,可以看出async/await可以说是异步终极解决方案了,最后看一下async/await用得最多的场景:

如果一个业务需要很多个异步操作组成,并且每个步骤都依赖于上一步的执行结果,这里采用不同的延时来体现:

//首先定义一个延时函数
function delay(time) {
  return new Promise(resolve => {
    setTimeout(() => resolve(time), time);
  });
}
//采用promise链式调用实现
delay(500).then(result => {
  return delay(result + 1000)
}).then(result => {
  return delay(result + 2000)
}).then(result => {
  console.log(result)  //3500ms后打印出3500
}).catch(error => {
  console.log(error)
})
//采用async实现
async function f(){
 const r1 = await delay(500)
 const r2 = await delay(r1+1000)
 const r3 = await delay(r2+2000)
 return r3
}
f().then(res =>{
 console.log(res)
}).catch(err=>{
 console.log(err)
})

可以看出,采用promise实现采用了很多then进行不停的链式调用,使得代码变得冗长和复杂且没有语义化。而 async/await首先使用同步的方法来写异步,代码非常清晰直观,而且使代码语义化,一眼就能看出代码执行的顺序,最后 async 函数自带执行器,执行的时候无需手动加载。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • JS基于ES6新特性async await进行异步处理操作示例

    本文实例讲述了JS基于ES6新特性async await进行异步处理操作.分享给大家供大家参考,具体如下: 我们往往在项目中会遇到这样的业务需求,就是首先先进行一个ajax请求,然后再进行下一个ajax请求,而下一个请求需要使用上一个请求得到的数据,请求少了还好说,如果多了,就要一层一层的嵌套,就好像有点callback的写法了,那是相当恶心的,下面我就来讲一下如何使用ES6的新特性async await进行异步处理,使上述情况就好像写同步代码一样,首先我们先举个例子: 先写上json文件: c

  • 微信小程序 es6-promise.js封装请求与处理异步进程

    微信小程序 es6-promise.js封装请求与处理异步进程 下载es6-promise.js置于根目录下的libs文件夹下: 在根目录utils文件夹下新建httpsPromisify.js,即定义封装请求的方法 var Promise = require('../libs/es6-promise.min') function httpsPromisify(fn) { return function (obj = {}) { return new Promise((resolve, reje

  • 详谈 Jquery Ajax异步处理Json数据.

    所谓的Ajax.这里我们讲二种方法 方法一:(微软有自带Ajax框架) 在Asp.net里微软有自己的Ajax框架.就是在页面后台.cs文件里引入 using System.Web.Services 空间 然后定义静态方法(方法前加上 [WebMethod]) [WebMethod] public static string ABC(string ABC) { return ABC; } 好了,现在我们谈谈前台Js怎么处理后台返回的数据吧,可利用Jquery处理返回的纯html,json,Xml

  • js中同步与异步处理的方法和区别总结

    在使用异步请求时,有时需要将异步请求的结果返回给另一个js函数,此种情况下会出现未等异步请求返回请求结果,该发送请求所在js函数已经执行完后续操作,即已经执行return ,这样会导致return的结果为空字符. 总结:若要在使用ajax请求后处理发送请求返回的结果,最好使用同步请求. 例如:以下例子会出现返回结果不正确的情况,因为ajax异步请求还未执行完,函数已经执行return了, 复制代码 代码如下: function fn(){ var result = " "; $.aja

  • javascript异步处理工作机制详解

    从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的.计时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的.我们先来认识一下下面三个函数是如何控制计时器的. var id = setTimeout(fn, delay); - 初始化一个计时器,然后在指定的时间间隔后执行.该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器. var id = setInterval(fn, delay); - 和setTimeout有些类

  • js 异步处理进度条

    1.先上图,效果如下: 2.使用方法 复制代码 代码如下: var loader=new Ajaxloader(this._ContentID,{Text:'loading......',Top:50}); loader.Show(); 3.代码列出: 复制代码 代码如下: /* 处理进度条,异步加载器 */ var Ajaxloader=new Class(); Ajaxloader.prototype= { Text:'数据加载中......', Parent:null, Left:0, T

  • jQuery Ajax异步处理Json数据详解

    先我们来看一个官方的实例使用 AJAX 请求来获得 JSON 数据,并输出结果: 复制代码 代码如下: $("button").click(function(){  $.getJSON("demo_ajax_json.js",function(result){    $.each(result, function(i, field){      $("div").append(field + " ");    });  });

  • JS异步处理的进化史深入讲解

    前言 javascript是一门单线程的语言,也就是说一次只能完成一件任务,如果有多个任务,就需要排队进行处理.如果一个任务耗时很长,后面的任务也必须排队等待,这样大大的影响了整个程序的执行.为了解决这个问题,javascript语言将任务分为两种模式: 同步:当我们打开网站,网页的页面骨架渲染和页面元素渲染,就是一大推同步任务. 异步:我们在浏览新闻时,加载图片或音乐之类占用资源大且耗时久的任务就是异步任务. 本文主要针对近两年javascript的发展,主要介绍异步处理的进化史.目前,在ja

  • Node.js 异步编程之 Callback介绍(一)

    Node.js 基于 JavaScript 引擎 v8,是单线程的.Node.js 采用了与通常 Web 上的 JavaScript 异步编程的方式来处理会造成阻塞的I/O操作.在 Node.js 中读取文件.访问数据库.网络请求等等都有可能是异步的.对于 Node.js 新人或者从其他语言背景迁移到 Node.js 上的开发者来说,异步编程是比较痛苦的一部分.本章将由浅入深为大家讲解 Node.js 异步编程的方方面面.从最基础的 callback 到 thunk.Promise.co 直到

  • 原生javascript实现文件异步上传的实例讲解

    效果图: 代码:(demo33.jsp) <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>demo33.jsp</title> </head> <body> <label for="text">名称</label>

  • 浅析JS异步加载进度条

    展现效果: 1) 当点击Load的时候,模拟执行异步加载. 浏览器被遮挡. 进度条出现. 实现思路: 1.当用户点击load button执行异步请求. 调用方法 出现加载条 2.怎么实现进度条呢? 1) 在document.body 新增一个div.覆盖浏览器. 设置背景会灰色. z-index = 999. 加载的时候让用户无法修改界面值 2) 在document.body 新增一个动态的div. 代码实现: Main.html: <!DOCTYPE html> <html>

  • js中变量的连续赋值(实例讲解)

    今天遇到了一个连续赋值的经典案例,网友们给出的答案也是五花八门,看起来有些繁琐,我也来说说自己的看法. 下面就是这个经典案例: var a = {n: 1}: var b = a; a.x = a = {n: 2}: console.log(a); console.log(b); console.log(a.x); console.log(b.x): 我们先来看一下普通连续赋值,即:变量赋值的类型是数据类型值 var a=3; var b=a=5; console.log(a); console

  • vue.js异步上传文件前后端实现代码

    本文实例为大家分享了vue.js异步上传文件的具体代码,供大家参考,具体内容如下 上传文件前端代码如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <meta charset="utf-8&qu

  • js实现随机点名系统(实例讲解)

    废话不多说,直接上代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>随机点名</title> <style type="text/css"> td{ text-align: center; } </style> </head> <body

  • 不同js异步函数同步的实现方法

    不同函数达到同步的函数模拟 funcList是函数执行函数的队列,其中回调函数中flag=true是同步标记量 <script> var flag = false; function funcTest(t,func){ setTimeout(function(){ (function(param){ console.log(param); func(); }(t)); },t*1000); } var funcList = []; funcList.push(function(){funcTe

  • jquery.form.js异步提交表单详解

    本文实例为大家分享了jquery.form.js异步提交表单的具体代码,供大家参考,具体内容如下 引入脚本: <script type="text/javascript" src="/js/jquery.form.min.js"></script> <script src="/js/json2.js"></script> //前端代码: <form id="f1">

  • JS异步文件分片断点上传的实现思路

    在项目中有时会遇到大文件上传,经常会出现链接超时的问题,所以就需要使用文件分片上传的方式来上传大文件.实现原理就是,在前端将文件分成指定大小的"文件块",分成多次上传,每次上传前先向后台查询已经上传成功的文件的大小,然后从指定的位置切割一块文件,进行上传,后台接收到文件块后追加到指定的文件中. 这篇博客适合有一些后台基础的朋友,或者有后台配合的前端. 这里需要后台提供两个接口,一个上传文件的接口,一个查询已上传文件大小的接口. 前端代码:https://github.com/li545

随机推荐