Generator

熟悉ES6语法的同学们肯定对Generator(生成器)函数不陌生,这是一个化异步为同步的利器。

栗子:

function* abc() {
let count = 0;
while(true) {
let msg = yield ++count;
console.log(msg);
}
} let iter = abc();
console.log(iter.next().value);
// 1
console.log(iter.next('abc').value);
// 'abc'
// 2

首先,我们先简单回顾一下JS的运行规则:

  1. JS是单线程的,只有一个主线程
  2. 函数内的代码从上到下顺序执行,遇到被调用的函数先进入被调用函数执行,待完成后继续执行
  3. 遇到异步事件,浏览器另开一个线程,主线程继续执行,待结果返回后,执行回调函数

那么,Generator函数是如何进行异步化为同步操作的呢?

实质上很简单,* 和 yield 是一个标识符,在浏览器进行软编译的时候,遇到这两个符号,自动进行了代码转换:

// 异步函数
function asy() {
$.ajax({
url: 'test.txt',
dataType: 'text',
success() {
console.log("我是异步代码");
}
})
} function* gener() {
let asy = yield asy();
yield console.log("我是同步代码");
}
let it = gener().next();
it.then(function() {
it.next();
})
// 我是异步代码
// 我是同步代码
// 浏览器编译之后
function gener() {
// let asy = yield asy(); 替换为
$.ajax({
url: 'test.txt',
dataType: 'text',
success() {
console.log("我是异步代码");
// next 之后执行以下
console.log("我是同步代码");
}
})
// yield console.log("我是同步代码");
}

整个过程类似于,浏览器遇到标识符 * 之后,就明白这个函数是生成器函数,一旦遇到 yield 标识符,就会将以后的函数放入此异步函数之内,待异步返回结果后再进行执行。

更深一步,从内存上来讲:

普通函数在被调用时,JS 引擎会创建一个栈帧,在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码),在当前栈帧里设置好返回位置,然后将新帧压入栈顶。待函数执行结束后,这个栈帧将被弹出栈然后销毁,返回值会被传给上一个栈帧。

当执行到 yield 语句时,Generator 的栈帧同样会被弹出栈外,但Generator在这里耍了个花招——它在堆里保存了栈帧的引用(或拷贝)!这样当 it.next 方法被调用时,JS引擎便不会重新创建一个栈帧,而是把堆里的栈帧直接入栈。因为栈帧里保存了函数执行所需的全部上下文以及当前执行的位置,所以当这一切都被恢复如初之时,就好像程序从原本暂停的地方继续向前执行了。

而因为每次 yield 和 it.next 都对应一次出栈和入栈,所以可以直接利用已有的栈机制,实现值的传出和传入。

至此,Generator 的魔力已经揭开。

Promise

Promise的用法大家应该都很熟悉:

let pr = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("成功执行啦");
}, 2000)
})
pr.then(function(data) {
console.log(data); // 成功执行啦
})

那么 Promise 是如何实现异步加载的呢?

Promise 并没有大家想的那么神秘,其本质就是一个状态机。

想要实现一个土生土长的 Promise 其实很简单,状态机,我们需要几个参数:

  • __success_res 用来存储成功时的参数
  • __error_res 用来存储失败时的参数
  • __status 用来存储状态
  • __watchList 用来存储执行队列

下面就手动实现一个 Promise

class Promise1 {
constructor(fn) {
// 执行队列
this.__watchList = [];
// 成功结果
this.__success_res = null;
// 失败结果
this.__error_res = null;
// 状态
this.__status = "";
fn((...args) => {
// 保存成功数据
this.__success_res = args;
// 状态改为成功
this.__status = "success";
// 若为异步则回头执行then成功方法
this.__watchList.forEach(element => {
element.fn1(...args);
});
}, (...args) => {
// 保存失败数据
this.__error_res = args;
// 状态改为失败
this.__status = "error";
// 若为异步则回头执行then失败方法
this.__watchList.forEach(element => {
element.fn2(...args);
});
});
} // then 函数
then(fn1, fn2) {
if (this.__status === "success") {
fn1(...this.__success_res);
} else if (this.__status === "error") {
fn2(...this.__error_res);
} else {
this.__watchList.push({
fn1,
fn2
})
}
}
}

这样就简单实现了 Promise 的功能,在使用上和JS的 Promise 并无其他区别,若想实现 Promise.all 方法,则只需要进行小小的迭代:

Promise1.all = function(arr) {
// 存放结果集
let result = [];
return Promise1(function(resolve, reject) {
let i = 0;
// 进行迭代执行
function next() {
arr[i].then(function(res) {
// 存放每个方法的返回值
result.push(res);
i++;
// 若全部执行完
if (i === result.length) {
// 执行then回调
resolve(result);
} else {
// 继续迭代
next();
}
}, reject)
}
})
}

至此,Generator 和 Promise 都已解析完成。

浅谈Generator和Promise原理及实现的更多相关文章

  1. 浅谈ES6原生Promise

    浅谈ES6原生Promise 转载 作者:samchowgo 链接:https://segmentfault.com/a/1190000006708151 ES6标准出炉之前,一个幽灵,回调的幽灵,游 ...

  2. TODO:浅谈pm2基本工作原理

    TODO:浅谈pm2基本工作原理 要谈Node.js pm2的工作原理,需要先来了解撒旦(Satan)和上帝(God)的关系. 撒旦(Satan),主要指<圣经>中的堕天使(也称堕天使撒旦 ...

  3. 浅谈SpringBoot核心注解原理

    SpringBoot核心注解原理 今天跟大家来探讨下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot为什么不需要XML,达到零配置 ...

  4. 浅谈springboot自动配置原理

    前言 springboot自动配置关键在于@SpringBootApplication注解,启动类之所以作为项目启动的入口,也是因为该注解,下面浅谈下这个注解的作用和实现原理 @SpringBootA ...

  5. 浅谈 session 会话的原理

    先谈 cookie 网络传输基于的Http协议,是无状态的协议,即每次连接断开后再去连接,服务器是无法判断此次连接的客户端是谁. 如果每次数据传输都需要进行连接和断开,那造成的开销是很巨大的. 为了解 ...

  6. 浅谈Javascript中Promise对象的实现

    https://segmentfault.com/a/1190000000684654 What? Promise是CommonJS的规范之一,拥有resolve.reject.done.fail.t ...

  7. 浅谈jQuery的promise

    jquery中的Promise,也就是我们所知道的Deferred对象. 举例1: var data=""; function runAsync(){ var def = $.De ...

  8. 浅谈JavaScript DDOS 攻击原理与防御

    前言 DDoS(又名"分布式拒绝服务")攻击历史由来已久,但却被黑客广泛应用.我们可以这样定义典型的DDoS攻击:攻击者指使大量主机向服务器发送数据,直到超出处理能力进而无暇处理正 ...

  9. 浅谈HashMap 的底层原理

    本文整理自漫画:什么是HashMap? -小灰的文章 .已获得作者授权. HashMap 是一个用于存储Key-Value 键值对的集合,每一个键值对也叫做Entry.这些个Entry 分散存储在一个 ...

随机推荐

  1. js确保正确this的几种写法

    1.直接用bind调用 this.method.bind(this) 2.构造函数中用bind定义 class Foo{ constructor(){ this.method = this.metho ...

  2. Sabota?

    Sabota? 题目描述 某个公司有n个人, 上下级关系构成了一个有根树.其中有个人是叛徒(这个人不知道是谁).对于一个人, 如果他下属(直接或者间接, 不包括他自己)中叛徒占的比例超过x,那么这个人 ...

  3. bzoj3105【CQOI2013】新nim游戏

    题意:http://www.lydsy.com/JudgeOnline/problem.php?id=3105 sol  :要想必胜则拿完后异或空间不能包含0,即给对手留下一组线性基 为保证拿走的最小 ...

  4. 【转】Linux C函数库参考

    asctime(将时间和日期以字符串格式表示)clock(取得进程占用CPU的大约时间)ctime(将时间和日期以字符串格式表示)difftime(计算时间差距)ftime(取得目前的时间和日期)ge ...

  5. windows 批处理删除指定目录下 指定类型 指定天数之前文件

    删除D:\test下5天前所有文件,如下: @echo offset SrcDir=D:\testset DaysAgo=5forfiles /p %SrcDir% /s /m *.* /d -%Da ...

  6. vscode编辑器开发react时,设置使emmet支持自定义组件

    "emmet.triggerExpansionOnTab": true 在vscode用户配置当中配置这个,就可以了

  7. 2017最好的JavaScript框架、库和工具 — SitePoint

    与开发者数量相比,可能有更多的JavaScript框架.库和工具.截止到2017年5月,在GitHub上快速搜索能搜到超过110万的JavaScript项目. 在npmjs上有50万的可用包,并且这些 ...

  8. Linux脚本中调用SQL,RMAN脚本

    Linux/Unix shell脚本中调用或执行SQL,RMAN 等为自动化作业以及多次反复执行提供了极大的便利,因此通过Linux/Unix shell来完成Oracle的相关工作,也是DBA必不可 ...

  9. 遍历页面所有的Checkbox,显示选中的ID

    原文发布时间为:2009-04-13 -- 来源于本人的百度文章 [由搬家工具导入] 1、 foreach (Control objCtrl in this.Page.Controls[3].Cont ...

  10. FCKeditor2.6.4控件及其使用范例

    原文发布时间为:2009-06-14 -- 来源于本人的百度文章 [由搬家工具导入] 已经精简了,很小,不多说了,我的源代码去下载看。 web.config 看看。。。还有bin里面的dll就是控件, ...