Table of contents

  • 前言
    • 同步与异步
      • 同步
      • 异步
    • callback
      • 多重嵌套
    • promise
    • 异步(async)函数
    • 参考资料

前言

JS 中最基础的异步调用方式是 callback,它将回调函数 callback 传给异步 API,由浏览器或 Node 在异步完成后,通知 JS 引擎调用 callback。对于简单的异步操作,用 callback 实现,是够用的。但随着负责交互页面和 Node 出现,callback 方案的弊端开始浮现出来。 Promise 规范孕育而生,并被纳入 ES6 的规范中。后来 ES7 又在 Promise 的基础上将 async 函数纳入标准。此为 JavaScript 异步进化史。


同步与异步

同步

通常,代码是由上往下依次执行的。如果有多个任务,就必需排队,前一个任务完成,后一个任务才会执行。这种执行模式称之为:同步(synchronous)。新手容易把计算机用语中的同步,和日常用语中的同步弄混淆。如,“把文件同步到云端”中的同步,指的是“使…保持一致”。而在计算机中,同步指的是任务从上往下依次执行的模式。比如:

A();
B();
C();

在这段代码中,A、B、C是三个不同的函数,每个函数都是一个不相关的任务。在同步模式,计算机会先执行 A 任务,再执行 B 任务,最后执行 C 任务。在大部分情况,同步模式都没问题。但是如果 B 任务是一个耗时很长的网络请求,而 C 任务恰好是展现新页面,就会导致网页卡顿。

异步

更好解决方案是,将 B 任务分成两个部分。一部分立即执行网络请求的任务,另一部分在请求回来后的执行任务。这种一部分立即执行,另一部分在未来执行的模式称为异步。

A();
// 在现在发送请求
ajax('url1',function B() {// 在未来某个时刻执行
})
C();
// 执行顺序 A => C => B

实际上,JS 引擎并没有直接处理网络请求的任务,它只是调用了浏览器的网络请求接口,由浏览器发送网络请求并监听返回的数据。JavaScript 异步能力的本质是浏览器或 Node 的多线程能力。


callback

未来执行的函数通常也叫 callback。使用 callback 的异步模式,解决了阻塞的问题,但是也带来了一些其他问题。在最开始,我们的函数是从上往下书写的,也是从上往下执行的,这种“线性”模式,非常符合我们的思维习惯,但是现在却被 callback 打断了!在上面一段代码中,现在它跳过 B 任务先执行了 C任务!这种异步“非线性”的代码会比同步“线性”的代码,更难阅读,因此也更容易滋生 BUG。
试着判断下面这段代码的执行顺序,你会对“非线性”代码比“线性”代码更难以阅读,体会更深。

A();ajax('url1', function(){B();ajax('url2', function(){C();}D();});
E();
// A => E => B => D => C

这段代码中,从上往下执行的顺序被 Callback 打乱了。我们的阅读代码视线是A => B => C => D => E,但是执行顺序却是A => E => B => D => C,这就是非线性代码带来的糟糕之处。

多重嵌套

通过将ajax后面执行的任务提前,可以更容易看懂代码的执行顺序。虽然代码因为嵌套看起来不美观,但现在的执行顺序却是从上到下的“线性”方式。这种技巧在写多重嵌套的代码时,是非常有用的。

A();
E();ajax('url1', function(){B();D();ajax('url2', function(){C();}});
// A => E => B => D => C

上一段代码只有处理了成功回调,并没处理异常回调。接下来,把异常处理回调加上,再来讨论代码“线性”执行的问题。

A();ajax('url1', function(){B();ajax('url2', function(){C();},function(){D();});},function(){E();});

加上异常处理回调后,url1的成功回调函数 B 和异常回调函数 E,被分开了。这种“非线性”的情况又出现了。

在 node 中,为了解决的异常回调导致的“非线性”的问题,制定了错误优先的策略。node 中 callback 的第一个参数,专门用于判断是否发生异常

A();get('url1', function(error){if(error){E();}else {B();get('url2', function(error){if(error){D();}else{C();}});}
});

到此,callback 引起的“非线性”问题基本得到解决。遗憾的是,使用 callback 嵌套,一层层if else和回调函数,一旦嵌套层数多起来,阅读起来不是很方便。此外,callback 一旦出现异常,只能在当前回调函数内部处理异常。


promise

在 JavaScript 的异步进化史中,涌现出一系列解决 callback 弊端的库,而 Promise 成为了最终的胜者,并成功地被引入了 ES6 中。它将提供了一个更好的“线性”书写方式,并解决了异步异常只能在当前回调中被捕获的问题。

Promise 就像一个中介,它承诺会将一个可信任的异步结果返回。首先 Promise 和异步接口签订一个协议,成功时,调用resolve函数通知 Promise,异常时,调用reject通知 Promise。另一方面 Promise 和 callback 也签订一个协议,由 Promise 在将来返回可信任的值给then和catch中注册的 callback。

// 创建一个 Promise 实例(异步接口和 Promise 签订协议)
var promise = new Promise(function (resolve,reject) {ajax('url',resolve,reject);
});// 调用实例的 then catch 方法 (成功回调、异常回调与 Promise 签订协议)
promise.then(function(value) {// success
}).catch(function (error) {// error
})

Promise 是个非常不错的中介,它只返回可信的信息给 callback。它对第三方异步库的结果进行了一些加工,保证了 callback 一定会被异步调用,且只会被调用一次。

var promise1 = new Promise(function (resolve) {// 可能由于某些原因导致同步调用resolve('B');
});
// promise依旧会异步执行
promise1.then(function(value){console.log(value)
});
console.log('A');
// A B (先 A 后 B)var promise2 = new Promise(function (resolve) {// 成功回调被通知了2次setTimeout(function(){resolve();},0)
});
// promise只会调用一次
promise2.then(function(){console.log('A')
});
// A (只有一个)var promise3 = new Promise(function (resolve,reject) {// 成功回调先被通知,又通知了失败回调setTimeout(function(){resolve();reject();},0)});
// promise只会调用成功回调
promise3.then(function(){console.log('A')
}).catch(function(){console.log('B')
});
// A(只有A)

介绍完 Promise 的特性后,来看看它如何利用链式调用,解决异步代码可读性的问题的。

var fetch = function(url){// 返回一个新的 Promise 实例return new Promise(function (resolve,reject) {ajax(url,resolve,reject);});
}A();
fetch('url1').then(function(){B();// 返回一个新的 Promise 实例return fetch('url2');
}).catch(function(){// 异常的时候也可以返回一个新的 Promise 实例return fetch('url2');// 使用链式写法调用这个新的 Promise 实例的 then 方法
}).then(function() {C();// 继续返回一个新的 Promise 实例...
})
// A B C ...

如此反复,不断返回一个 Promise 对象,再采用链式调用的方式不断地调用。使 Promise 摆脱了 callback 层层嵌套的问题和异步代码“非线性”执行的问题。

Promise 解决的另外一个难点是 callback 只能捕获当前错误异常。Promise 和 callback 不同,每个 callback 只能知道自己的报错情况,但 Promise ***着所有的 callback,所有 callback 的报错,都可以由 Promise 统一处理。所以,可以通过catch来捕获之前未捕获的异常。

Promise 解决了 callback 的异步调用问题,但 Promise 并没有摆脱 callback,它只是将 callback 放到一个可以信任的中间机构,这个中间机构去链接我们的代码和异步接口。


异步(async)函数

异步(async)函数是 ES7 的一个新的特性,它结合了 Promise,让我们摆脱 callback 的束缚,直接用类同步的“线性”方式,写异步函数。

声明异步函数,只需在普通函数前添加一个关键字 async 即可,如async function main(){} 。在异步函数中,可以使用await关键字,表示等待后面表达式的执行结果,一般后面的表达式是 Promise 实例。

async function main{// timer 是在上一个例子中定义的var value = await timer(100);console.log(value); // done (100ms 后返回 done)
}main();

异步函数和普通函数一样调用 main() 。调用后,会立即执行异步函数中的第一行代码 var value = await timer(100)。等到异步执行完成后,才会执行下一行代码。

除此之外,异步函数和其他函数基本类似,它使用try…catch来捕捉异常。也可以传入参数。但不要在异步函数中使用return来返回值。

var  timer = new Promise(function create(resolve,reject) {if(typeof delay !== 'number'){reject(new Error('type error'));}setTimeout(resolve,delay,'done');
});async function main(delay){try{var value1 = await timer(delay);var value2 = await timer('');var value3 = await timer(delay);}catch(err){console.error(err);// Error: type error//   at create (<anonymous>:5:14)//   at timer (<anonymous>:3:10)//   at A (<anonymous>:12:10)}
}
main(0);

异步函数也可以被当作值,传入普通函数和异步函数中执行。但是在异步函数中,使用异步函数时要注意,如果不使用await,异步函数会被同步执行。

async function main(delay){var value1 = await timer(delay);console.log('A')
}async function doAsync(main){main(0);console.log('B')
}doAsync(main);
// B A

这个时候打印出来的值是 B A。说明 doAsync 函数并没有等待 main 的异步执行完毕就执行了 console。如果要让 console 在 main 的异步执行完毕后才执行,我们需要在main前添加关键字await。

async function main(delay){var value1 = await timer(delay);console.log('A')
}async function doAsync(main){await main(0);console.log('B')
}doAsync(main);
// A B

由于异步函数采用类同步的书写方法,所以在处理多个并发请求,新手可能会像下面一样书写。这样会导致url2的请求必需等到url1的请求回来后才会发送。

var fetch = function (url) {return new Promise(function (resolve,reject) {ajax(url,resolve,reject);});
}async function main(){try{var value1 = await fetch('url1');var value2 = await fetch('url2');conosle.log(value1,value2);}catch(err){console.error(err)}
}main();

使用Promise.all的方法来解决这个问题。Promise.all用于将多个Promise实例,包装成一个新的 Promis e实例,当所有的 Promise 成功后才会触发Promise.all的resolve函数,当有一个失败,则立即调用Promise.all的reject函数。

var fetch = function (url) {return new Promise(function (resolve,reject) {ajax(url,resolve,reject);});
}async function main(){try{var arrValue = await Promise.all[fetch('url1'),fetch('url2')];conosle.log(arrValue[0],arrValue[1]);}catch(err){console.error(err)}
}main();

参考资料

Promise对象
Nowcoder例题-Promise

JS中关于异步的那点事~相关推荐

  1. [js] js循环中调用异步的方法,如何确保执行结果的顺序是正确的?

    [js] js循环中调用异步的方法,如何确保执行结果的顺序是正确的? An example:let searchApi = function(arg){return new Promise((reso ...

  2. JS中同步和异步的区别

    JS中同步和异步的区别 js是一门单线程语言,所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推.如果一个任务耗 ...

  3. 一个讨论引发关于js中函数声明,函数表达式,形参与变量声明赋值引发的一些事(http://www.cnblogs.com/zhouyongtao/archive/2012/11/22/2783089)

    //Firefox 17下运行结果 function a(){} undefined [function a(){}, 2, 3] ee 444 6 ========================= ...

  4. JS笔记(20): JS中的同步编程和异步编程

    铺垫:关于定时器 定时器:设定一个定时器,并且设定了等到的时间,当到达指定的时间,浏览器会把对应的方法执行 1)常用的定时器 1.setTimeout(function,intarval) 执行一次 ...

  5. react性能优化方案_React灵敏且性能卓越的Spray + Akka解决方案,以“在Java和Node.js中发挥并发性和性能”...

    react性能优化方案 在我以前的文章中,我研究了一个虚拟的交易引擎,并将基于Java的阻止解决方案与基于Node.js的非阻止解决方案进行了比较. 在文章的结尾,我写道: 我怀疑随着Node.js的 ...

  6. 反应灵敏且性能卓越的Spray + Akka解决方案,以“在Java和Node.js中发挥并发性和性能”...

    在我以前的文章中,我研究了一个虚拟的交易引擎,并将基于Java的阻止解决方案与基于Node.js的非阻止解决方案进行了比较. 在文章的结尾,我写道: 我怀疑在Node.js近期取得成功之后,越来越多的 ...

  7. 如何在js中使用ajax请求数据,在 JS 中怎么使用 Ajax 来进行请求

    在 JS 中怎么使用 Ajax 来进行请求 发布时间:2021-07-22 09:48:43 来源:亿速云 阅读:78 作者:chen 本篇内容介绍了"在 JS 中怎么使用 Ajax 来进行 ...

  8. JS中同步和异步问题及其区别

    1.同步和异步 首先,我们浏览器常用的是JS语言,但其实相比于JAVA来说,JS其实是单线程语言,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需 ...

  9. 如何在js中使用ajax请求数据,在 JS 中如何使用 Ajax 来进行请求

    做者:Danny Markov 译者:前端小智 来源:tutorialzine 点赞再看,微信搜索 [大迁世界]关注这个没有大厂背景,但有着一股向上积极心态人.本文 GitHub https://gi ...

最新文章

  1. python pandas inplace用法
  2. EMC NetWorker备份oracle安装配置指南
  3. 新手入门深度学习 | 2-3:文本数据建模流程示例
  4. TCP对应的应用层协议之FTP /SMTP / HTTP
  5. C++基础知识(四)—— 操作符/运算符
  6. POJ 1797 Heavy Transportation
  7. springboot2.2.9源码搭建
  8. 高性能nginx HTTP服务器 配置实例(转自我的收藏)
  9. C#.Net工作笔记008---c# 日期时间大小比较_日期相减
  10. python统计图像灰度直方图_python 对一幅灰度图像进行直方图均衡化
  11. Android Studio单元测试入门
  12. 【优化求解】基于matlab改进的粒子群算法求解混合储能系统容量优化问题【含Matlab源码 1823期】
  13. 花生壳:域名诊断—客户端离线
  14. vue-echart简单使用
  15. 一款免费、炫酷的GUI:AWTK
  16. Python网络爬虫简单入门
  17. Mac OX 下方便的微信语音,图片,视频等导出方式
  18. 英语口语收集(二十六)
  19. 拿下阿里自动化测试岗23k*14薪offer的全程面试记录解析以及总结,一面二面三面,项目,功能,自动化,性能测试,面试题问答
  20. 看看人家那公众号管理系统,那叫一个优雅(附源码)

热门文章

  1. MTCNN可攻破?华为提出一种可复制、可靠的攻击方法
  2. php定时执行代码漏洞_在CTF比赛中发现的PHP远程代码执行0day漏洞
  3. 收藏 | 综述:目标检测二十年
  4. python复习-正则表达式
  5. 梯度下降法快速教程 | 第二章:冲量(momentum)的原理与Python实现
  6. 机器学习(十五)隐马尔科夫模型-未完待续
  7. caffe 日志保存以及matlab绘制方法(windows以及ubuntu下)
  8. java for遍历hashmap_Java中HashMap遍历几种方式
  9. 最详细的Fast RCNN论文笔记
  10. querydsl动态 sql_Spring data jpa 复杂动态查询方式总结