【ECMAScript6】es6 要点(二)Promise | 自个写一个Promise | Generator | Async/Await 下

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 【ECMAScript6】es6 要点(二)Promise | 自个写一个Promise | Generator | Async/Await

Generator

Generator不是一种函数式编程技术,但它是函数的一部分,因为函数式编程正是围绕着函数的技术。

我们前面提到,Promise是用于处理回调问题的技术,但是,随着ES6的发展与支持Generator,已经可以不需要Promise。

生成器,一种特殊类型的函数, 一种语法糖

  • 普通函数:一路到底
  • generator函数:中间能停
//示例1
# 带星号:即在函数名称前使用一个星号来表示这是一个Generator函数。
function* show(){//创建一个generator对象
  alert('a');
  yield;//暂停
  alert('b');
}
let genObj = show(); //调用 Generator函数,生成一个迭代器,从而能够控制生成器的执行
console.log(genObj);//返回一个Generator原始类型的实例
genObj.next(); // 调用实例的next()函数,从Generator实例genObj中获取一个值,即:执行alert('a');
//如果再一次.next()就执行alert('b');

但是,我们不能无限制地调用next从Generator实例中获取值。否则最后会返回undefined。原因:Generator犹如一种序列,一旦序列中的值被消费,你就不能再次消费它。即,序列为空后,再次调用就会返回undefined!。

那么,要怎么能够才能再次消费呢?——需要另外再创建一个Generator实例。例如:

let genObj2 = show();

因此,迭代器用于控制生成器的执行,迭代器对象暴露的最基本接口是next方法。这个方法可以用来向生成器请求一个值,从而控制生成器。next函数被调用后,生成器就开始执行代码,当代码直行道yield关键字时,就会生成一个中间结果(生成值序列中的一项),然后返回一个新对象,其中封装了结果值(value)和一个指示完成的指示器(done)。

每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值请求鄂到达,这是普通函数完全不具备的特性。

Generator是如何转换的

//示例2
 function* show(){
     let a = 12;
     let data1 = yield $.ajax('data/1.txt');
     let b = 5;
     let data2 = yield $.ajax('data/2.txt');
     return a+b;
  }
  //实际上,转换为:
  function show() {
     let a =12;
     $.ajax('data/1.txt').then(res=>{
        let data1 = res;
     })
     let b = 5;
     $.ajax('data/2.txt').then(res=>{
        let data2 = res;
        return a+b;
     })
  }

适合场景

  • 请求数据
  • 最大特点:让一个函数走走停停

重点:关键字 yield

yield 使Generator函数暂停了执行并将结果返回给调用者。第一次调用Generator实例时,yield将函数置于暂停模式并返回值。当下一次调用Generator实例时,Generator函数将从它中断的地方恢复执行。

用一段代码和对应的一张图演示Generator的操作序列:

function* genetatorSeq() {
  yield 'first';
  yield 'second';
  yield 'third';
}
let genSeq = genetatorSeq();
console.log(genSeq.next().value); //first
console.log(genSeq.next().value); //second
console.log(genSeq.next().value); //third

需要注意的是:所有带有yield的Generator都会以惰性求值的顺序执行。

何为惰性求值

它指的是:代码直到调用时才会执行。即,当我们需要时,相应的值才会被计算并返回。

  • 可以传参和返回
function* show(num1,num2) {
  alert('a');
  let a =yield;
  alert('b');
  alert(a);//5
}
let gen = show(1,2);
gen.next(12);//没法给yeild传参
gen.next(5);//参数会被传递到
  • 返回
function* show() {
  alert('a');
  yield 12;
  alert('b');
  return 1;
}
let gen = show();
let res1= gen.next();
console.log(res1); 
//Obejct {value:12,done:false;}
let res2= gen.next(); 
console.log(res2);
//Object {value:1,done:true}
//value来自return,若没有return 则value为undefined
function* 炒菜(菜市场买回来的){
  洗菜->洗好的菜
  let 干净的菜= yeild 洗好的菜
  干净的菜->切菜->丝
  let 切好的菜 = yeild 丝;
  切好的菜->炒->熟的菜
  return 熟的菜;
}

done属性

每次对next函数调用,都将返回一个如下示例的对象:

{value:'value',done:false}

其中,

  • value来自Generator的值;
  • done是一个判断Generator序列是否已经被完全消费掉了的属性。当done的值为true时就应该停止调用Generator实例的next。

generator 实例

生成ID序列

function* countNum() {
  let i=0;
  while(true){
    yield ++i;
  }
}
const countGenerator = countNum();//生成一个迭代器
const obj1 = {
  id: countGenerator.next().value
}
const obj2 = {
  id: countGenerator.next().value
}
const obj3 = {
  id: countGenerator.next().value
}
console.log(obj1)
console.log(obj2)
console.log(obj3)
  • 处理异步操作

实例1

//httpGetAsync 通过node中的https模块触发一个Ajax调用以便获取响应
let https = require('https');
function httpGetAsync(url,callback) {
  return https.get(url,
    function(response) {
      let body = '';
      response.on('data',function(d) {
        body+=d;
      });
      response.on('end',function(d) {
        let parsed = JSON.parse(body);
        callback(parsed);
      });
  });
}
//request: 将httpGetAsync封装到一个单独的方法request中
function request(url) {
  httpGetAsync(url,function(response){
    generator.next(response);//用generator的next调用替换回调
  });
}
//main : 将业务需求封装到一个Generator函数内部
function* main() {
//调用yield将暂停函数执行,直到request通过接收Ajax的响应调用generator的next
  let json1 = yield request("https://www.reddit.com/r/pics/.json");
  let data1 = yield request(json1.data.children[0].data.url+".json")
  console.log(data1);
}
//运行
let gen = main();
gen.next();

实例2

cnpm i yield-runner-blue //获取文件夹中的index.js

数据读取

好处:像写同步操作一样写一步操作

runner(function* (){
  let data1 = yield $.ajax({url:'1.txt',dataType:'json'});//yield 出一个Promise对象给runner,然后执行返回给data1
  let data2 = yield $.ajax({url:'2.txt',dataType:'json'});
  let data3 = yield $.ajax({url:'3.txt',dataType:'json'});
  console.log(data1,data2,data3);
});

几种异步操作:

  • 回调
  • Promise
  • Generator

Generator对比Promise:

  • Promise 带逻辑
Promise.all([
  $.ajax({url:'getUserData',dataType:'json'})
]).then(results=>{
  let userData = results[0];
  if(userData.type=='vip'){
     Promise.all([
      $.ajax({url:'getVipItems',dataType:'json'})
]).then(results=>{
  let items = results[0];
  //生成列表
},err=>{
  alert('fail');
})
  }
},err=>{
  alert('失败');
})
  • Generator 带逻辑
runner(function* (){
  let userData = yield $.ajax({url:'getUserData',dataType:'json'});
  if(userData.type=='vip'){
  let items = yield $.ajax({url:'getVipItems',dataType:'json'});
  }else {
  let items = yield $.ajax({url:'getItems',dataType:'json'});
  }
  //生成列表...
})

综上,generator适合处理逻辑性判断,Promise适合一次读一堆。另外,generator是对Promise一个封装。

用迭代器遍历DOM树

<body>
  <div id="subTree">
    <form>
      <input type="text" name="" />
    </form>
    <p>Paragraph</p>
    <span>Span</span>
  </div>
  <script type="text/javascript">
    function* DomTraversal(element){
      yield element;
      element = element.firstElementChild;
      while(element) {
        yield* DomTraversal(element); //用yield* 将迭代器控制器转移到另一个DomTraversal生成器实例上
        element = element.nextElementSibling;
      }
    }
    const subTree = document.getElementById("subTree");
    for(let element of DomTraversal(subTree)){//使用for-of 对节点进行循环迭代
      console.log(element)
    }
  </script>
</body>

Generator在KOA中的应用

https://koa.bootcss.com/

下载koa 与 koa-mysql

cnpm i koa
cnpm i koa-mysql
const koa = require('koa');//使用require引入koa库
const mysql = require('koa-mysql');
let db=mysql.createPool({host:'localhost',user:'root',password:'123456',dataBase
:'test'});//创建mysql
let server = new koa(); //创建一个服务
//generator在KOA中的应用
server.use(function *(){
  //this.body = 'abc';//在页面上输出abc
  let data = yield db.query('SELECT * FROM user_table');//查询user_table
  this.body = data;
});
server.listen(8080); //服务器监听

Generator 内部构造

调用一个生成器不会实际执行它。相反,它创建了一个新的迭代器,通过该迭代器我们才能从生成器中请求值。在生成器生成了一个之后,生成器会进入挂起执行并等待下一个请求到来的状态。从某种方面上说,生成器的工作更像一个状态机。

它分别有4种状态:

  • 挂起开始:创建一个生成器后,它最先以这种状态开始。其中的任何代码并没有执行。
  • 执行:生成器中的代码已开始执行。可能是刚开始执行,也可能是从上次挂起的时候继续执行。当生成器对应的迭代器调用了next()方法时,并且当前存在可执行的代码,生成器就会转移到这个状态。
  • 挂起让渡:当生成器在执行过程中遇到一个yield表达式,它会创建一个包含返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
  • 完成:在生成器执行期间,如果代码执行到return语句,或者全部代码执行完,生成器就会进入完成状态。

不同于标准函数,每次退出后就会销毁,生成器中,只要我们从生成器中取得控制权,生成器的执行环境上下文一直是保存的。

Generator和Promise结合

将生成器和Promise结合,能实现更加优雅的代码。例如:我们可以把异步任务放在生成器中,然后执行生成器函数。因为没法知道Promise什么时候会被resolved,所以生成器执行的时候,我们需要将执行权让渡给生成器,从而不会造成UI阻塞。当Promise被resolved,我们会继续通过迭代器的next函数执行生成器。

async(function* (){
  try {
    const a = yield getJSON('a.json');
    const b = yield getJSON('b.json');
    const c = yield getJSON('c.json');
  }catch(e){
    console.log("Error:",e);
  }
});
//辅助函数,对生成器执行操作
function async(generator) {
  let iterator = generator(); //创建一个迭代器,进而控制生成器
  function handle(iteratorResult) {//handle对生成器产生的每个值进行处理
    if(iteratorResult.done) {return;}//生成器没有结果时停止执行
    const iteratorValue = iteratorResult.value;
    if(iteratorValue instanceof Promise) {//如果生成器的值是一个Promise,则对其注册成功和失败回调
      //如果promise成功返回,则恢复生成器的执行并传入Promise的返回结果
      //遇到错误,向生成器抛出异常
      iteratorValue.then(res=>handle(iterator.next(res)))
             .catch(err=>iterator.throw(err));
    }
  }
  try {
    handle(iterator.next());//重启生成器的执行
  }catch(e) {
    iterator.throw(e);
  }
}

由上述代码我们知道:

  • 函数是一等对象:向async函数传入函数参数
  • 生成器函数:它的特性可以用于挂起和恢复执行
  • Promise:帮助处理异步代码
  • 回调函数:在Promise对象上注册成功和失败的回调函数
  • 箭头函数:适合用在回调函数上
  • 闭包:迭代器在async函数内被创建,在promise的回调函数内通过闭包获取该迭代器

generator+promise 异步请求

function* exportGenerator(data){
      let queryContent = "requestUrl";
      let content = yield getJSON(queryContent)//返回一个Promise对象
      return content;
}
let btn = docment.querySelector('.btn');
btn.addEventListener('click',function(){
  const exportInterator = exportGenerator();
  exportInterator.next().value.then(res=>{
    //todo List
  }).catch(e=>console.log("Error:",e));
})
//promise
function getJSON(url) {
    return new Promise((resolve,reject)=>{
      const request = new XMLHttpRequest();//创建一个XMLHttpRequest对象
      request.open("GET",url);//初始化请求
      request.onload = function() {//注册一个onload方法,当服务器响应后被钓鱼那个
        try {
          if(this.status===200){
            resolve(JSON.parse(this.response));//尝试解析字符串
          }else {
            reject(this.status+" "+this.statusText);
          }
        }catch(e) {
          reject(e.message);
        }
      };
      request.onerror = function() {//和服务器通信发生错误
        reject(this.status+" "+this.statusText);
      };
      request.send();
    })
  }

Async/Await

从上面看到,我们仍然要写一些样板代码。因为每次需要的时候都要复用,但如果不关心这个过程就好了!

通过在关键字function之前使用关键字async,可以表明当前的函数依赖一个异步返回的值,在每个调用异步任务的位置上,都要放置一个await关键字,用于告诉javascript引擎,请在不阻塞应用执行的情况下在这个位置上等待执行结果

(async function(){
  try {
    const a = await getJSON('a.json');
    const b = await getJSON('b.json');
  }catch(e){
    console.log("Error:",e);
  }
})
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
4月前
|
前端开发 JavaScript API
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!
在前端开发中,异步编程至关重要。本文详解了同步与异步的区别,通过生活化例子帮助理解。深入讲解了 Promise 的概念、状态及链式调用,并引入 async/await 这一语法糖,使异步代码更清晰易读。还介绍了多个异步任务的组合处理方式,如 Promise.all 与 Promise.race。掌握这些内容,将大幅提升你的异步编程能力,写出更优雅、易维护的代码,助力开发与面试!
262 0
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!
|
4月前
|
前端开发 JavaScript API
JavaScript异步编程:从Promise到async/await
JavaScript异步编程:从Promise到async/await
486 204
|
10月前
|
前端开发
使用 async/await 结合 try/catch 处理 Promise.reject()抛出的错误时,有什么需要注意的地方?
使用 async/await 结合 try/catch 处理 Promise.reject()抛出的错误时,有什么需要注意的地方?
415 57
|
前端开发 JavaScript 开发者
前端开发中的异步编程:Promise 和 Async/Await 的比较与应用
在现代前端开发中,异步编程是不可或缺的技术。本文将深入探讨Promise和Async/Await这两种主流的异步编程方式,分析它们的优劣势及在实际项目中的应用场景。通过比较它们的语法、可读性和错误处理机制,帮助开发者更好地选择和理解如何在项目中高效地利用这些技术。
|
前端开发 JavaScript 开发者
Async 和 Await 是基于 Promise 实现
【10月更文挑战第30天】Async和Await是基于Promise实现的语法糖,它们通过简洁的语法形式,借助Promise的异步处理机制,为JavaScript开发者提供了一种更优雅、更易于理解和维护的异步编程方式。
272 1
|
前端开发
如何使用async/await解决Promise的缺点?
总的来说,`async/await` 是对 Promise 的一种很好的补充和扩展,它为我们提供了更高效、更易读、更易维护的异步编程方式。通过合理地运用 `async/await`,我们可以更好地解决 Promise 的一些缺点,提升异步代码的质量和开发效率。
259 64
|
前端开发 JavaScript
async/await和Promise在性能上有什么区别?
性能优化是一个综合性的工作,除了考虑异步模式的选择外,还需要关注代码的优化、资源的合理利用等方面。
375 63
|
JSON 前端开发 JavaScript
浅谈JavaScript中的Promise、Async和Await
【10月更文挑战第30天】Promise、Async和Await是JavaScript中强大的异步编程工具,它们各自具有独特的优势和适用场景,开发者可以根据具体的项目需求和代码风格选择合适的方式来处理异步操作,从而编写出更加高效、可读和易于维护的JavaScript代码。
321 1
|
前端开发 JavaScript
setTimeout、Promise、Async/Await 的区别
`setTimeout` 是用于延迟执行函数的简单方法;`Promise` 表示异步操作的最终完成或失败;`Async/Await` 是基于 Promise 的语法糖,使异步代码更易读和维护。三者都用于处理异步操作,但使用场景和语法有所不同。