异步的解决方案

回调

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
简单的说回调函数是参数传递函数,并指定它在响应某个事件(定时器、鼠标点 击、Ajax 响应等)时执行这个参数传递的函数。这个参数就是回调。回头调用。回调传参

假定有两个函数f1和f2,后者等待前者的执行结果。
如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。

    function f1(callback){
    setTimeout(function () {
      // f1的任务代码
      callback(); // 这是回调   因为是setTimeout计时后触发的。
    }, 1000);
  }
    function f1(callback){
   callback(); // 这不是回调   因为是callback()自己直接调用的。
  }

执行代码就变成下面这样:f1(f2);
回调会造成的问题:

  • 层次的嵌套,俗称回调金字塔

还有什么问题吗?仅仅是代码看起来混乱吗?

顺序的大脑
//伪代码
doA(function(){
  doB();
  doC(function() {
    doD();
  });
  doE();
});
doF();

无论多么熟悉JS异步的人,要完全搞懂这段代码实际的运行顺序,恐怕也得思考一番。
为什么会这样? → 因为人的大脑是顺序的,天生适合顺序的思考,难以理解(不是不能理解)非顺序的东西。
无论是在书写还是在阅读这段代码的时候,我们的大脑都会下意识地以为这段代码的执行逻辑是这样的doA→doB→doC→doD→doE→doF,然而实际运行逻辑很可能(假设)是这样的doA→doF→doB→doC→doE→doD

思想实验

我们来进行两种游戏

  1. 第一种游戏,举办方提前跟你说:“这游戏总共有X关。第一关你应该做.....然后在....(地方)进入第二关。第二关你应该做....然后在....(地方)进入第三关。……"。我称之为呆板的游戏。
  2. 第二种游戏,举办方提前跟你说:”你只管从这个门口进去,等你快到下一关的时候,自然会有人出来给你提示。“我称之为灵活的游戏。
    对应到代码当中,我们便能发现回调的另一个严重问题:硬编码。
    前后的操作被回调强制硬编码绑定到一起了。在调用函数A的时候,你必须指定A结束之后该干什么,并且显式地传递进去。这样,其实你已经指定了所有的可能事件和路径,代码将变得僵硬且难以维护。同时,在阅读代码的时候,由于必须记住前后的关联操作,这也加重了大脑记忆的负担

Promise

Promise 是 ES 2015 原生支持的,他把原来嵌套的回调改为了级联的方式。

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})
a.then(function(val) {
    console.log(val)
})

如果要涉及到多个异步操作的顺序执行问题,我们可以这样写:

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})

a
  .then(function(val){
    console.log(val)
    return new Promise(function(resolve, reject) { // 必须加return不然promise不止到这个then函数中又是个promise。只有通过返回值判断一下。
      setTimeout(function() {
          resolve('2')
      }, 2000)
    })
  })
  .then(function(val) {
    console.log(val)
  })

以Ajax为例。我们都知道,在Ajax执行成功之后,指定的回调函数会被放入”任务队列“中。JS执行引擎在主线程空闲的时候便会轮询任务队列,执行其中的任务。
我们仔细想想,是不是漏了一个关键点:”我知道最终是JS引擎执行了这个回调函数。但是,到底是谁调度这个回调函数的?到底是谁在特定的时间点把这个回调函数放入任务队列中去?“
答案是宿主环境,在本例中也就是浏览器。是浏览器检测到Ajax已经成功返回,是浏览器主动将指定的回调函数放到”任务队列”中,JS引擎只不过是执行而已。
由此,我们澄清了一件(可能令人震惊)的事情: 在回调时代,尽管你已经能够编写异步代码了。但是,其实JS本身,从来没有真正內建直接的异步概念,直到ES6的出现。
事实就是如此。JS引擎本身所做的只不过是在不断轮询任务队列,然后执行其中的任务。JS引擎根本不能做到自己主动把任务放到任务队列中,任务的调度从来都是宿主完成的。举个形象的例子就是:“JS引擎就像是流水线上的工人,宿主就像是派活的老板。工人只知道不断地干活,不断地完成流水线上出现的任务,这些任务都是老板给工人指定的。工人从来没有(也不能)自己给自己派活,自己给自己的流水线上放任务。”
ES6从本质上改变了在哪里管理事件循环,这意味着在技术上将其纳入了JavaScript引擎的势力范围,而不再是由宿主来管理。
为什么说ES6的Promise才真正有了异步呢?因为Promise是基于任务队列(job queue)。可以js方面的在事件循环中插队。并不是宿主环境控制的。
Promise 是一个 Job,所以必然异步的,因为 then 总是返回 Promise,xxx.then(a => a) 的效果实际上是 return new Promise(resolve => resolve(a)),所以then也是异步。

es6引入了Generator

为什么会出现Generator

本质上,generator是一个函数,它执行的结果是一个iterator迭代器,每一次调用迭代器的next方法,就会产生一个新的值。迭代器本身就是用来生成一系列值的,同时也广泛应用于扩展运算符...、解构赋值和for...of循环遍历等地方。

generator函数的函数是分段的。第一次执行next的时候,程序会执行到第一个yield,然后返回{ value:1, done:false },表示yield后面返回1,但是函数Hello还没执行完,函数既不会退出,也不会往下执行。

function p(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(new Date());
    }, time)
  });
}

function* delay(){
  let time1 = yield p(1000);
  console.log(1, time1);

  let time2 = yield p(1000);
  console.log(2, time2)

  let time3 = yield p(2000);
  console.log(3, time3);
}

function co(gen){
  let it = gen();
  next();
  function next(arg){
    let ret = it.next(arg);
    if(ret.done) return;
    ret.value.then((data) => {
      next(data)
    })
  }
}

co(delay);// 这里写的co函数只适用于yield后跟promise。

co库的简单实现

await/async

ES7中提供async函数,利用它,不需要依赖co库,也一样可以解决这个问题。
使用方法和 co 非常类似,同时也支持同步写法的异常捕获。async的返回值为Promise 对象。

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 2000)
  })
}

var b = async function() {
  var val = await a()
  console.log(val)
}

b()

使用await/async或者Generator进行http请求的时候经常是配合这Promise的。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

async既可以处理promise又可以处理普通的,不过此时相当于同步,而且更有语义化。

async function asyncPrint() {
  await yibu1();
  await yibu2();
}

使用async/await yibu1请求完了再去请求yibu2。如果使用Promise。是不等yibu1的结果就去请求yibu2。就等于async可以把本身的回调或者then里的内容拿出来当做同步代码写。但是一般http请求函数都没有返回值,只有请求有了结果才会有结果,所以await后一般跟着promise。不然await也不知道异步函数请求结束了呀。

yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

所以

async function hhh () {
    var ll = await setTimeout(function() {
        console.log('ss')
    }, 3000)
    console.log('ll')
}
hhh()

回调主要有两个问题,信任问题和顺序问题,Promise解决信任问题,Generator解决顺序问题。但我认为Generator也不是把顺序问题完完全全的解决了。因为如果

async function async1(value, ms) {
  console.log(value)
  await new Promise()
  async function async2(value, ms) {
      // 
  }
  console.log(value1)
} 

假设 console.log(value1)本身是一个很费时的同步任务,并且不需要等待async2执行完成。这时候,我们还是会先执行console.log(value1) ,后执行async2的回调。await会使得async函数卡住不动,所以只能解决一层一层的嵌套的顺序问题,不能解决这种同一级别的顺序问题。
async函数的返回值是一个Promise,上面这个问题可以改为

async function async1(value, ms) {
  console.log(value)
  await new Promise()
  console.log(value1)
} 
async1().then(function(){
  async2(value, ms) 
})

四种异步解决方案
回调与generate

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容