JavaScript的异步发展(callback,Promise,Generator,async/await)

3 篇文章 0 订阅
1 篇文章 0 订阅


JS异步的发展

对JS有所了解的人都知道,JS语言是单线程的,简单来说就是指一次只能完成一件任务。如果同时有许多个任务,就必须排队,前一个任务耗时长,但是后一个任务必须等待前一个任务完成。

单线程
比如,在做饺子时,必须先准备好馅和面团,才能开始包饺子。这就是单线程。
在这里插入图片描述

多线程
任务1和任务2可以同时做就是多线程。
在这里插入图片描述
因为js是单线程的,所以就有了同步异步的概念。

同步
就是一按顺序一步步执行。
在这里插入图片描述
异步
在这里插入图片描述
现在我们对单线程多线程同步异步有了了解,我们来看看异步的发展时间线:
在这里插入图片描述
本文从发展时间线对异步的这四个点对异步做了介绍:

  • callback
  • Promise
  • Generator
  • async/await

一. callback

  1. 什么是回调函数?

A callback is a function that is passed as an argument to another
function and is executed after its parent function has completed.

callback函数是一个以参数形式传递给另一个函数的函数,并且callback函数必须等另一个函数执行完才会被调用!(当被调用时,另一个函数就是callback函数的父函数)。

  1. 回调函数中的异步
  • 首先,我们来了解一下回调
    在这里插入图片描述

调用函数b时,执行顺序就b函数中从上到下的顺序,先执行语句1、2,而执行到callback时回调回去先去调用a函数,当a函数执行完毕后,继续执行语句3、4callback()语句放在任何一个语句后面都遵循上述原则。

  • 回调的例子(无异步
//最简单的例子
function canYueHui(callback) { 
    console.log('我可以去约会吗?');
    callback();
 }
 function goTo() { 
     console.log('sure!快去吧~别让约会对象等急了');
  }
  function canNotGo() { 
      console.log('不可以哦~因为你长的不好看');
   }
   canYueHui(goTo);
   //输出 
    //我可以去约会吗?
    // sure!快去吧~别让约会对象等急了
   canYueHui(canNotGo);
   //输出
    // 我可以去约会吗?
    // 不可以哦~因为你长的不好看
    
  • 上面就是一个简单的例子,传入一个函数作为参数,然后在函数体中调用这个函数,就是一种callback的回调
  • 然后在canYueHui函数的内部调用goTo函数来得到结果。
  • callback中完成异步调用
    下面代码,getArray函数用来打印数组,而addArray函数用来给数组添加元素。
// 复杂一些的例子
const array = ['我','是','一','个'];
function getArray() { 
  setTimeout(()=>{
    array.forEach((ele)=>{
      console.log(ele);
    },1000);
  })
 }
// 无回调函数callback的时候 不可以添加函数
function addArray(element){
    setTimeout(()=>{
      array.push(element);
    },2000);
  }
  getArray();
  addArray('回调函数');
  // 输出
  // 我 是 一 个
  

上述代码中的addArray是没有使用callback的情况。先打印了array中的内容,然后在进行添加元素。所以输出中没有回调函数四个字。延迟也只有一秒钟。

 //先进行函数之后在运行回调函数 即 先运行addArray之后 在调用getArray函数
function addArray(element,callback){
  setTimeout(()=>{
    array.push(element);
    callback();
  },2000);
}
addArray('回调函数',getArray);
// 输出 
// 我 是 一 个 回调函数

上述代码是使用callback调用addArray的情况。给addArray函数传了getArray作为参数,并在函数中调用该函数。使得打印是在添加元素之后进行,输出延迟了三秒钟,先进行函数之后在运行回调函数,即在setTimeout中先运行了.push(),在运行getArray函数。

  1. 优点

解决了同步问题(即解决了一个任务时间长时,后面的任务排队,耗时太久,使程序的执行变慢问题)

  1. 缺点
  • 回调地狱(多层级嵌套)
    • 会导致逻辑混乱
    • 耦合性高,改动一处就会导致全部变动
  • 可读性差
function sayNum(name, callback) {
        setTimeout(() => {
            console.log(name);
            callback()
        }, 1000)
    }
    sayNum('one', function () {
        sayNum('two', function () {
            sayNum('three', function () {
                sayNum('four', function () {
                    console.log('结束了');
                })
            })
        })
    })
//打印结果 one two three four 结束了

JavaScript中Promise的提出提出解决了callback中的回调地狱问题


二. Promise

使用Promise解决上面的代码

function f(num) {
        return new Promise(function (resolve, reject) {
            setTimeout(() => {
                console.log(num);
                resolve(num);// 成功执行时返回正确的值。
            }, 1000)
        })
    }
    
    var p1 = f('one')
    p1.then((data) => {
            return f('two')
        })
        .then((data) => {
            return f('three')
        })
        .then((data) => {
            return f('four')
        })
//  打印结果  one two three four

代码清晰可观,解决了逻辑混乱的问题

  1. 概念
  • Promise 就是一个对象,用来传递异步操作的信息。
  • 主要用于异步计算
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  • 可以在对象之间传递和操作promise,帮助我们处理队列
  1. Promise对象
    2.1 三个状态之间的 转化
    在这里插入图片描述
    2.2 对象创建的介绍
  • Promise构建函数接受一个函数作为参数,一个是resolve,一个reject.
  • resolve的是将pending状态转为fulfilled状态,而reject是转为rejected状态
  1. .then和.catch
  • then
  • 实例生成后,可以使用then方法分别来指定这两个状态的回调函数.
  • then方法可以接受两个回调作为参数
  • then返回的是一个新的Promise对象

在这里插入图片描述

  • resolve的返回值作为then中的参数1传入
  • reject的返回值作为then中的参数2传入
let testPromise = new Promise((resolve,reject)=>{
    let a = true;
    if (a){
        resolve('这是正确返回');
    }else{
        reject('这是一个报错');
    }
});
testPromise.then((a)=>{
    console.log(a);
},(err)=>{
    console.log(err);
})
// a = false时 输出 这是一个报错
// a = true时 输出  这是正确返回

  • 上述代码就是简单的例子,首先创建一个Promise对象,然后在a为true时调用resolve回调函数,反之,则调用reject回调函数。
  • catch
  • catch是用来接受在Promise对象中抛出的错误的
const testCatch = new Promise((resolve,reject)=>{
    throw new Error('只是一个简单的报错');
}).catch((err)=>{
    console.log(err);
})
// 输出 只是一个简单的报错

  • 上述代码就展示了抛出一个错误时,会调用catch
  • 当然也可以在Promise的参数函数中使用 try...catch来接收这个错误
  1. 优点
  • 一旦状态改变,就不会再变任何时候都可以得到这个结果
  • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
  • 一定程度上解决了回调地狱的问题
  1. 缺点
  • 无法取消 Promise
  • 当处于pending状态时,无法得知目前进展到哪一个阶段
  • 代码冗余,有一堆任务时也会使语义不清晰
  • 每一个 then 方法内部是一个独立的作用域,要是想共享数据,就要将部分数据暴露在最外层,在then 内部赋值一次

三. Generator

为什么引入一个Generator?

我们来看看generator中异步的使用

  1. 概念
  • Generator函数是ES6中提供的一种异步编程解决方案。语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态,需要使用next()函数来继续执行下面的代码。
  1. 两个特征
  • function与函数名之间带有(*)
  • 函数体内部使用yield表达式,函数执行遇到yield就返回。
  1. 例子
  • 下面是一个简单的Generator举例(无异步情况
const helloGenerator = function* (){ 
    yield 'hello';
    yield 'Generator';
    return 'I Love You';
 }
const testGenerator = helloGenerator();
 console.log(testGenerator);
 //Object [Generator] {}
 console.log(testGenerator.next());
 //{ value: 'hello', done: false }
 console.log(testGenerator.next());
 //{ value: 'Generator', done: false }
 console.log(testGenerator.next());
 //{ value: 'I Love You', done: true }
 console.log(testGenerator.next());
 //{ value: undefined, done: true }
 
  • 上述代码就创建了一个名为helloGeneratorGenerator函数,代码中有两个yield,一个return
  • 第一次调用 .next()输出的是遇到第一个yield时返回后面的hello,而返回的对象就是valuedonevalue返回的就是yield后面的值,而donefalse,则表示遍历还没有结束。
  • 第二次调用.next()返回的是第二个yield后面的Generator
  • 第三次调用.next()返回的则是return之后的内容。如果遇到return之后,就不会在继续执行下面的内容了,也就是说到return之后就结束了done变为true.
  • 第四次调用.next()函数的value都是undefined,且done都为true.
function* gen(){
    var url = 'https://api.github.com/users/github';
    var result = yield fetch(url);
    console.log(result);
  }

  var g = gen();
  var result = g.next();
  
  result.value.then(function(data){
    return data.json();
  }).then(function(data){
    g.next(data);
  });
  

依据Promise的原理来实现异步,配合.thenGenerator函数.next来实现异步。

  1. 优点

可以控制函数的执行,可以配合co函数库使用

  1. 缺点
  • 流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)
  • 需要手动使用next()语句

async/await是对Generator的包装,是它的提升。


四. async/await

Generator异步的比较

 async function gainSth(){
    var result1 = await fetch(url);
    var result2 = await fetch(url);
    var result3 = await fetch(url);
    var result4 = await fetch(url);
  }

下面的三个await会在第一个fetch函数执行完毕之后才继续执行。简单的说,将异步改为了“同步”。

  1. 概念

也是用来处理异步的,是Generator函数的改进,背后的原理是Promise。稍微与上面比较一下,可以发现async函数就是将Generator
函数的星号(*)替换成async,将yield替换成了await

  1. 相比于Generator函数的四个改进
  • 内置执行器。意味着不需要像Generator一样调用next函数或co模块。
  • 语义更好。使用async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果
  • 更广的适用性。asyncawait后面跟的都是Promise函数,原始数据类型会被转为Promise
  • 返回值是Promiseasync返回值是Promise。可以直接使用.then.catch
  1. 简单举例
  • fun1函数返回resolve
// 单独 async的使用
async function fun1() { 
    return 'ni';
    // 等价于 
    return new Promise((resolve) => { resolve('ni'); });
    // 等价于
    return Promise.resolve('ni');
 }
 console.log(fun1());
 // 输出 
 // Promise { 'ni' }
 

上述代码只使用了async建立了一个函数fun1,内部没有使用await,运行一下可以发现返回的是一个Promise对象。上述三种return的写法都是等价的。

 //asyns/await结合使用
 async function testAwait() { 
     var knowAwait = await fun1();
     console.log('123');
     console.log(knowAwait);
  }
  testAwait()
  // 123
  // ni
  

上面代码结合了asyncawait建立了一个函数testAwait.函数头加上async,在函数内部使用await。遇到await时,会阻塞后面的代码先返回,等异步操作执行完成时再来继续执行后面的语句。

  • fun1返回reject
async function fun2(){
    return Promise.reject('NO');
}
async function testReject() { 
    try{
        var isReject = await fun2();
    }catch(error){
        console.log(error);
    }
    console.log('123');
 }
 testReject();
 // 输出
 // NO
 // 123
 

如果返回的时reject时,后面的代码就不会在执行,并产生错误,原因就是NO,要加上try...catch才可以执行

  1. 优点
  • 相比于Promise,简化了then的链式写法
  • Generator函数的封装,实现最简洁、语义话
  1. 缺点
  • await是阻塞后面的代码,写多了容易造成性能问题

五. 异步总结

  • JS异步发展从callback--->Promise--->Generator--->async/await
异步发展优点缺点
callback解决了同步问题1. 回调地狱 1.1 导致逻辑混乱1.2 耦合性高,牵一发而动全身 2. 可读性差
promise1. 解决了回调地狱 2. 可读性好 3. 状态一经改变,不会再变1. 无法取消promise 2. 处于pending状态时,无法得知目前到哪一个阶段 3. 代码冗余 语义不清 4. 每一个 then 方法内部是一个独立的作用域,要是想共享数据,就要将部分数据暴露在最外层,在then 内部赋值一次
Generator可以控制函数的执行,可以配合co函数库使用1. 流程管理却不方便(即何时执行第一阶段、何时执行第二阶段) 2. 需要手动使用next()语句
async/await对Generator函数的封装,实现最简洁、语义话阻塞后面的代码,可能导致性能问题

async/await 与 callback

  • 相比较于callback,async/await也解决了同步问题,而且不会有回调地狱,代码耦合性弱,可读性好。

async/await 与 Promise

  • 写码时可以非常优雅地处理异步函数,彻底告别回调恶梦和无数的 then 方法
  • 每一个 then 方法内部是一个独立的作用域,要是想共享数据,就要将部分数据暴露在最外层,在then 内部赋值一次

async/await 与 Generator

  • async/awaitGenerator函数的一个语法糖,是对Generator函数的改进
  • async/await 自带执行机,只需要执行一次
  • async和await,比起(*)yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果
  • 返回的是Promiseasync函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖

所以目前来说,async/await是最好的异步解决方案了。

参考文献


纯手画手写,如果有问题希望指出,第一次写这么长的技术文档,可能有点偏离了异步的概念。
谢谢观看,有问题请指出!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值