前言
本篇文章适合前端架构师, 或者进阶的前端开发人员; 我在面试 vmware 前端架构师的时候, 被问到关于 callback,promise,generator,async-await 的问题.
首先我们回顾一下 javascript 异步的发展历程.
ES6 以前:
回调函数(callback);nodejs express 中常用, ajax 中常用.
ES6:
promise 对象; nodejs 最早有 bluebird promise 的雏形, axios 中常用.
generator 函数; nodejs koa 框架使用率很高.
ES7:
async/await 语法; 当前最常用的异步语法, nodejs koa2 完全使用该语法.
回调函数 callback
回调函数实际就是一个参数; 将一个函数当做参数传到另一个函数里, 当那个函数执行完后, 再执行传进去的这个函数; 这个过程就叫做回调.
回调字面也好理解, 就是先处理本体函数, 再处理回调的函数, 举个例子, 方便大家理解.
- function A(callback){
- console.log("我是主体函数");
- callback();
- }
- function B(){
- console.log("我是回调函数");
- }
- A(B);
- /* 输出结果
- 我是主体函数
- 我是回调函数
- */
上面的例子很好理解, 首先执行主体函数 A, 打印结果: 我是主题函数; 然后执行回调函数 callback 也就是 B, 打印结果: 我是回调函数.
promise 对象
promise 对象用于一个异步操作的最终完成 (或最终失败) 及其结果的表示.
简单地说就是处理一个异步请求. 我们经常会做些断言, 如果我赢了你就嫁给我, 如果输了我就嫁给你之类的断言. 这就是 promise 的中文含义: 断言, 一个成功, 一个失败.
举个例子, 方便大家理解:
promise 构造函数的参数是一个函数, 我们把它称为处理器函数, 处理器函数接收两个函数 reslove 和 reject 作为其参数, 当异步操作顺利执行则执行 reslove 函数, 当异步操作中发生异常时, 则执行 reject 函数. 通过 resolve 传入得的值, 可以在 then 方法中获取到, 通过 reject 传入的值可以在 chatch 方法中获取到.
因为 then 和 catch 都返回一个相同的 promise 对象, 所以可以进行链式调用.
- function readFileByPromise("a.txt"){
- // 显示返回一个 promise 对象
- return new Promise((resolve,reject)=>{
- fs.readFile(path,"utf8",function(err,data){
- if(err)
- reject(err);
- else
- resolve(data);
- })
- })
- }
- // 书写方式二
- readFileByPromise("a.txt").then( data =>{
- // 打印文件中的内容
- console.log(data);
- }).catch( error =>{
- // 抛出异常,
- console.log(error);
- })
generator 函数
ES6 的新特性 generator 函数(面试的时候挂在这里), 中文译为生成器, 在以前一个函数中的代码要么被调用, 要么不被调用, 还不存在能暂停的情况, generator 让代码暂停成待执行, 定义一个生成器很简单, 在函数名前加个 * 号, 使用上也与普通函数有区别.
举个例子, 方便大家理解:
- function *Calculate(a,b){
- let sum=a+b;
- console.log(sum);
- let sub=a-b;
- console.log(sub);
- }
上面便是一个简单的 generator 声明例子.
generator 函数不能直接调用, 直接调用 generator 函数会返回一个对象, 只有调用该对象的 next()方法才能执行函数里的代码.
let gen=Calculate(2,7);
执行该函数:
- gen.next();
- /* 打印
- 9
- -5
- */
其实单独介绍 generator 并没有太大的价值, 要配合 key yield, 才能真正发挥 generator 的价值. yield 能将生 Generator 函数的代码逻辑分割成多个部分, 下面改写上面的生成器函数.
- function *Calculate(a,b){
- let sum=a+b;
- yield console.log(sum);
- let sub=a-b;
- yield console.log(sub);
- }
- let gen=Calculate(2,7);
- gen.next();
- /* 输出
- 9*/
可以看到这段代码执行到第一个 yield 处就停止了, 如果要让里边所有的代码都执行完就得反复调用 next()方法
- let gen=Calculate(2,7);
- gen.next();
- gen.next();
- /* 输出
- 9
- -5*/
在用一个例子, 来说明 generator 函数与回调函数的区别:
回调函数:
- fs.readFile("a.txt",(err,data)=>{
- if(!err){
- console.log(data);
- fs.readFile("b.txt",(err,data)=>{
- if(!err)
- console.log(data);
- })
- }
- })
这是一个典型的回调嵌套, 过多的回调嵌套造成代码的可读性和可维护性大大降低, 形成了令人深恶痛绝的回调地狱, 试想如果有一天让你按顺序读取 10 个文件, 那就得嵌套 10 层, 再或者需求变更, 读取顺序要变了先读 b.txt, 再度 a.txt 那改来真的不要太爽.
generator 函数:
- function readFile(path) {
- fs.readFile(path,"utf8",function(err,data){
- it.next(data);
- })
- }
- function *main() {
- var result1 = yield readFile("a.txt");
- console.log(result1);
- var result2 = yield readFile("b.txt");
- console.log(result2);
- var result3 = yield readFile("c.txt");
- console.log(result3);
- }
- var it = main();
- it.next();
generator 函数的强大在于允许你通过一些实现细节来将异步过程隐藏起来, 依然使代码保持一个单线程, 同步语法的代码风格. 这样的语法使得我们能够很自然的方式表达我们程序的步骤 / 语句流程, 而不需要同时去操作一些异步的语法格式.
async-await
async 函数返回一个 promise 对象, 如果在 async 函数中返回一个直接量, async 会通过 Promise.resolve 封装成 Promise 对象.
我们可以通过调用 promise 对象的 then 方法, 获取这个直接量.
- async function test(){
- return "Hello World";
- }
- var result=test();
- console.log(result);
- // 打印 Promise { 'Hello World' }
那如过 async 函数不返回值, 又会是怎么样呢?
- async function test(){
- }
- var result=test();
- console.log(result);
- // 打印 Promise { undefined }
await 会暂停当前 async 的执行, await 会阻塞代码的执行, 直到 await 后的表达式处理完成, 代码才能继续往下执行.
await 后的表达式既可以是一个 Promise 对象, 也可以是任何要等待的值.
如果 await 等到的是一个 Promise 对象, await 就忙起来了, 它会阻塞后面的代码, 等着 Promise 对象 resolve, 然后得到 resolve 的值, 作为 await 表达式的运算结果.
上边你看到阻塞一词, 不要惊慌, async/await 只是一种语法糖, 代码执行与多个 callback 嵌套调用没有区别, 本质并不是同步代码, 它只是让你思考代码逻辑的时候能够以同步的思维去思考, 避开回调地狱, 简而言之 - async/await 是以同步的思维去写异步的代码, 所以 async/await 并不会影响 node 的并发数, 大家可以大胆的应用到项目中去!
如果它等到的不是一个 Promise 对象, 那 await 表达式的运算结果就是它等到的东西.
举个例子, 方便大家理解:
- function A() {
- return "Hello";
- }
- async function B(){
- return "World";
- }
- async function C(){
- // 等待一个字符串
- var s1=await A();
- // 等待一个 promise 对象, await 的返回值是 promise 对象 resolve 的值, 也就是 "World"
- var s2=await B();
- console.log(s1+s2);
- }
- C();
- // 打印 "Hello World"
来源: https://www.cnblogs.com/peiyu1988/p/9204976.html