Promise、Generator,Async/await
我们知道JavaScript是单线程语言,如果没有异步编程非得卡死。
以前,异步编程的方法有下面四种
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
现在据说异步编程终极解决方案是——async/await
发展史:
本人最先接触Promise是Jquery的Deferred对象,然后就出现了bluebird这类实现Promise A+规范的库,在后来就是ES6对他的原生实现,以及对Generator的加入,再后来async/await又出现了。
一、回调函数
所谓回调函数(callback),就是把任务分成两步完成,第二步单独写在一个函数里面,等到重新执行这个任务时,就直接调用这个函数。
例如Node.js中读取文件
fs.readFile('a,txt', (err,data) = >{
if(err) throw err;
console.log(data);
})
上面代码中readFile的第二个参数就是回调函数,等到读取完a.txt文件时,这个函数才会执行。
二、Promise
使用回调函数本身没有问题,但有“回调地狱”的问题。
假定我们有一个需求,读取完A文件之后读取B文件,再读取C文件,代码如下
fs.readFile(fileA, (err, data) => {
fs.readFile(fileB, (err, data) => {
fs.readFile(fileC, (err,data)=>{
//do something
})
});
});
可见,三个回调函数代码看来就够呛了,有时在实际业务中还不止嵌套这几个,难以管理。
这时候Promise出现了!它不是新的功能,而是一种新的写法,用来解决“回调地狱”的问题。
我们再假定一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果,用setTimeout()来模拟异步操作
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function A(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return A(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return A(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return A(n);
}
上面代码中有4个函数,A()返回一个Promise对象,接收参数n,n秒后执行resolve(n+200)。step1、 step2、step3对应三个步骤
现在用Promise实现这三个步骤:
function doIt() {
console.time('do it now')
const time1 = 300;
step1(time1)
.then( time2 =>step2(time2))
.then( time3 => step3(time3))
.then( result => {
console.log(`result is ${result}`)
});
}
doIt();
输出结果如下
step1 with 300
step2 with 500
step3 with 700
result is 900
result是step3()的参数700+200 = 900。
可见,Promise的写法只是回调函数的改进,用then()方法免去了嵌套,更为直观。
但这样写绝不是最好的,代码变得十分冗余,一堆的then。
所以,最优秀的解决方案是什么呢?
开头暴露了,就是async/await
讲async前我们先讲讲协程与Generator
三、协程
协程(coroutine),意思是多个线程相互协作,完成异步任务。
它的运行流程如下
- 协程A开始执行
- 协程A执行到一半,暂停执行,执行的权利转交给协程B。
- 一段时间后B交还执行权
- 协程A重得执行权,继续执行
上面的协程A就是一个异步任务,因为在执行过程中执行权被B抢了,被迫分成两步完成。
读取文件的协程代码如下:
function task() {
// 其他代码
var f = yield readFile('a.txt')
// 其他代码
}
task()函数就是一个协程,函数内部有个新单词yield,yield中文意思为退让,
顾名思义,它表示执行到此处,task协程该交出它的执行权了。也可以把yield命令理解为异步两个阶段的分界线。
协程遇到yield命令就会暂停,把执行权交给其他协程,等到执行权返回继续往后执行。最大的优点就是代码写法和同步操作几乎没有差别,只是多了yield命令。
这也是异步编程追求的,让它更像同步编程
四、Generator函数
Generator是协程在ES6的实现,最大的特点就是可以交出函数的执行权,懂得退让。
function* gen(x) {
var y = yield x +2;
return y;
}
var g = gen(1);
console.log( g.next()) // { value: 3, done: false }
console.log( g.next()) // { value: undefined, done: true }
上面代码中,函数多了*号,用来表示这是一个Generator函数,和普通函数不一样,不同之处在于执行它不会返回结果,
返回的是指针对象g,这个指针g有个next方法,调用它会执行异步任务的第一步。
对象中有两个值,value和done,value 属性是 yield 语句后面表达式的值,表示当前阶段的值,done表示是否Generator函数是否执行完毕。
下面看看Generator函数如何执行一个真实的异步任务
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then( data => return data.json)
.then (data => g.next(data))
上面代码中,首先执行Generator函数,得到对象g,调用next方法,此时
result ={ value: Promise { <pending> }, done: false }
因为fetch返回的是一个Promise对象,(即value是一个Promise对象)所以要用then才能调用下一个next方法。
虽然Generator将异步操作表示得很简洁,但是管理麻烦,何时执行第一阶段,又何时执行第二阶段?
是的,这时候到Async/await出现了!
五、Async/await
从回调函数,到Promise对象,再到Generator函数,JavaScript异步编程解决方案历程可谓辛酸,终于到了Async/await。很多人认为它是异步操作的最终解决方案(谢天谢地,这下不用再学新的解决方案了吧)
其实async函数就是Generator函数的语法糖,例如下面两个代码:
var gen = function* (){
var f1 = yield readFile('./a.txt');
var f2 = yield readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
var asyncReadFile = async function (){
var f1 = await readFile('./a.txt');
var f2 = await readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
上面的为Generator函数读取两个文件,下面为async/await读取,比较可发现,两个函数其实是一样的,async不过是把Generator函数的*号换成async,yield换成await。
1.async函数用法
上面说了async不过是Generator函数的语法糖,那为什么要取这个名字呢?自然是有理由的。
async是“异步”,而await是async wait的简写,即异步等待。所以应该很好理解async用于声明一个function是异步的,await用于等待一个异步方法执行完成
下面来看一个例子理解async命令的作用
async function test() {
return "async 有什么用?";
}
const result = test();
console.log(result)
输出:
Promise { 'async 有什么用?' }
可以看到,输出的是一个Promise对象!
所以,async函数返回的是一个Promise对象,如果直接return 一个直接量,async会把这个直接量通过PromIse.resolve()封装成Promise对象
注意点
一般来说,都认为await是在等待一个async函数完成,确切的说等待的是一个表示式,这个表达式的计算结果是Promise对象或者是其他值(没有限定是什么)
即await后面不仅可以接Promise,还可以接普通函数或者直接量。
同时,我们可以把async理解为一个运算符,用于组成表达式,表达式的结果取决于它等到的东西
- 等到非Promise对象 表达式结果为它等到的东西
- 等到Promise对象 await就会阻塞后面的代码,等待Promise对象resolve,取得resolve的值,作为表达式的结果
还是那个业务,分多个步骤完成,每个步骤依赖于上一个步骤的结果,用setTimeout模拟异步操作。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
async实现方法
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
输出结果和上面用Promise实现是一样的,但这个代码结构看起来清晰得多,几乎跟同步写法一样。
2. async函数的优点
(1)内置执行器
Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
(2) 语义化更好
async 和 await,比起星号和 yield,语义更清楚了。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
(3)更广的适用性
yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
Promise、Generator,Async/await的更多相关文章
- ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await
ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...
- JS异步编程 (2) - Promise、Generator、async/await
JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...
- Promise、Generator、Async有什么区别?
前言 我们知道Promise与Async/await函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到Promise处理异步,到Generator处理异步,再到Asyn ...
- C#中 Thread,Task,Async/Await 异步编程
什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调 ...
- C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿!
说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们 1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部 ...
- C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿![转载]
说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们 1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部 ...
- Thread,Task,async/await,IAsyncResult
1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部分可以同时执行:对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行,这样主 ...
- 详解C#中 Thread,Task,Async/Await,IAsyncResult的那些事儿
说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们 1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部 ...
- node传统读取文件和promise,async await,
先上传统文件加载方式代码,传统方式在处理多层嵌套时代码比较混乱 const fs = require('fs') //引入文件系统 function readFile (cb) { fs.readFi ...
随机推荐
- 每日一道 LeetCode (3):回文数
前文合集 每日一道 LeetCode 文章合集 题目:回文数 题目来源:https://leetcode-cn.com/problems/palindrome-number/ 判断一个整数是否是回文数 ...
- springboot多数据源启动报错:required a single bean, but 6 were found:
技术群: 816227112 参考:https://stackoverflow.com/questions/43455869/could-not-autowire-there-is-more-than ...
- 《分享》Koa2源码分析
曾经在公司内部做的一起关于koa源码的分享,希望对你有帮助: koa2 源码分析整理 koa2(2.4.1版本)源码主要包含四个js,包括application.js, context.js, req ...
- 2017面向对象程序设计(Java)第十七周助教工作总结
本学期已接近尾声,java课程也即将结束.经过一学期的java学习,相信大家已经从最初的懵懂.困惑渐渐的走向了柳暗花明,并对java的体系结构有了更加清晰的认识.但一学期的学习是远远不 ...
- JS解密入门——有道翻译
JS解密入门——有道翻译 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这 ...
- 类型SQL注入实验 Part1
准备为PHPstudy环境 <?php $id = $_GET['t']; $conn = mysql_connect("127.0.0.1","root" ...
- 动态路由 - EIGRP
EIGRP 特性 EIGRP(增强内部网关路由协议)是思科的私有协议,属于距离矢量路由协议,但又具有链路状态的特性.并且支持 VLSM(可变长子网和无类路由协议).但在本质上说还是传送路由条目. 具有 ...
- salesforce零基础学习(九十九)Git 在salesforce项目中的应用(vs code篇)
本篇参考: https://code.visualstudio.com/docs/editor/versioncontrol https://git-scm.com/doc https://git-s ...
- 2020-04-13:TCP协议本身会导致什么样的安全问题?
福哥答案2020-04-14: 洪泛攻击
- wordpress-技术博客主题推荐
推荐主题 1.WordStar 这个主题是干净的,以博客为中心,设计清晰,简单,直接的排版,可在各种各样的屏幕尺寸可读,适合多种语言. 效果图 还是非常简洁, 基本和CSDN差不多了 除了没有广告以外 ...