异步

2018-10-15  本文已影响0人  漂泊的小蘑菇

异步的来源

js是单线程的语言,所谓单线程即代码一行一行的执行,后面的代码必须等待前面的执行完毕才可以执行,对于普通的耗时短的代码来说可能没有什么问题,但是对于耗时长的一段代码来说,就会造成卡顿,比如说发起一个网络请求,请求的资源何时返回,这个时间是不可预估的,这种情况下会造成卡顿,所以js针对这种情况设计了异步。

处理异步的几种方式

回调函数

var ajax = $.ajax({
    url: '/a/b',
    success: function () {
        console.log('success')
    }
})

回调函数最常见的就是在ajax请求中,以上请求中传入两个参数,url和success,url是请求的路径,success是一个函数,success不会立马执行,而是等待请求成功之后才执行,这种就称为回调函数。
原理:
将回调函数作为参数传递给异步执行的函数,当有结果返回之后再触发回调函数。
弊端:
多个回调函数嵌套,会造成回调地狱现象,非常难以阅读和维护。

promise

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise对象是一个构造函数,用来生成promise实例,Promise接受一个函数作为参数,该函数有两个参数,resolve和reject,resolve的作用是将状态由未完成变为成功,reject是将状态变为失败。Promise实例生成之后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
原理:
每一个异步任务返回一个promise对象,该对象有一个then方法,允许指定回调函数。
优点:
回调函数写成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法。

Generator

在讲Generator函数的异步应用之前必须要先弄清楚迭代器和生成器的概念

iterator基本概念

iterator即迭代器,它是一种接口,为各种数据结构提供统一的访问机制,只要数据结构部署iterator接口,就可以实现遍历。
iterator是一个对象,它知道如何去访问集合中的一项,并且跟踪该序列中的当前位置,它提供一个next方法,该方法返回包含了value和done两个属性的对象, value 是当前成员的值,done是一个布尔值,表示遍历是否结束。

function iterator(array){
    var nextIndex = 0;
    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}

var it = iterator(['yo', 'ya']);
console.log(it.next()); // {value: 'yo', done: false}
console.log(it.next()); // {value: 'ya', done: false}
console.log(it.next()); // {value: undefined, done: true}

一个数据结构只要具有Symbol.iterator属性就是可迭代的(不管是本身还是原型链上有该属性),Symbol.iterator本身是一个函数,是当前数据结构的遍历器生成函数,当数据需要被迭代时,就会被调用然后返回一个用于在迭代中获得值的迭代器。

iterator调用场合

参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators

GeneratorFunction

由于自定义迭代器需要显式地维护其内部状态,比较繁琐,所以提供了GeneratorFunction(生成器函数)。
GeneratorFunction是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的Generator对象。 如果使用 function*语法,则函数会变成GeneratorFunction。

//idMaker 是一个GeneratorFunction
function* idMaker() {
  var index = 0;
  while(true)
    yield index++;
}

生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数,并不会马上执行内部的语句,而是会返回这个生成器的迭代器对象,当调用该迭代器对象的next方法时会执行内部语句,当遇到yield时就会暂停执行,yield后面紧跟该迭代器要返回的值,如果用的yield* ,则表示将执行权移交给另一个生成器函数,当前生成器暂停执行。

next()方法返回一个包含value和done两属性的对象,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。

function* generator(i) {
  yield i;
  yield 'hello';
  y = yield 'world';
  yield y;
  return 'ending';
}

var hw = generator(3);  // // "Generator { }"
hw.next();  // { value:3, done: false }
hw.next();  // { value: 'hello', done: false }
hw.next();  // { value: 'world', done: false }
hw.next(2);  // { value: 2, done: false }
hw.next();   // { value: 'ending', done: true }
hw.next();   // { value: undefined, done: true }

当在生成器函数中显式 return 时,会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 done 为 true。如果 return 后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值,如果没有则为undefined。

function* generator() {
  yield i;
  yield 'hello';
  return 2;
  y = yield 'world';
  yield y;
  return 'ending';
}

var hw = generator();  // "Generator { }"
hw.next();  // { value: 'hello', done: false }
hw.next();  // { value: 2, done: true }
hw.next();  // { value: undefined, done: true }
Generator函数的异步应用

Generator 函数是协程在 ES6 的实现,协程的意思是多个线程互相协作,完成异步任务,最大特点就是可以交出函数的执行权即暂停执行。
整个 Generator 函数就是一个封装的异步任务,需要暂停的地方,都用yield语句注明

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
}

var g = gen();
var result = g.next(); // result.value是一个promise

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

弊端:流程管理不方便,手动调用next方法

Generator 函数的自动流程管理

针对Generator 函数的流程管理不方便的弊端,提出了以下几个方法:

  1. 回调函数。 将异步操作包装成Thunk函数,在回调函数里面交回执行权
    前提:每一个异步操作都是都是Thunk函数,即yield命令之后的都要是Thunk函数。
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);  //转换为Thunk函数

// 生成器函数
var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  // ...
  var fn = yield readFileThunk('fileN');
};
// 自动执行next
function run(fn) {
  var gen = fn();
  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }
  next();
}

run(g);
  1. Promise对象,将异步操作包装成 Promise 对象,用then方法交回执行权
var fs = require('fs');
// 将异步操作包装成 Promise 对象
var readFile = function (fileName){
 return new Promise(function (resolve, reject){
   fs.readFile(fileName, function(error, data){
     if (error) return reject(error);
     resolve(data);
   });
 });
};
// 生成器函数
var gen = function* (){ 
 var f1 = yield readFile('/etc/fstab');
 var f2 = yield readFile('/etc/shells');
 console.log(f1.toString());
 console.log(f2.toString());
};
//用then方法交回执行权,调用next方法
function run(gen){
 var g = gen();

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

 next();
}

run(gen);

  1. co模块
    co模块就是将Thunk函数和Promise对象包装成一个模块,使用co模块的前提是yield之后只能是Thunk函数或者Promise对象,
var co = require('co');
// 生成器函数
var gen = function* () {
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
// co函数返回一个Promise对象,因此可以用then方法添加回调函数
co(gen).then(function (){
  console.log('Generator 函数执行完成');
});

参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield

async await

async函数是Generator函数的语法糖,他们的区别在于:

  1. async函数自带内置执行器
    Generator函数的执行必须要靠执行器,所以有了co模块,但是async函数自带执行器。
  2. async函数适用性更广
    使用co模块时,yield后面必须跟Thunk函数或者Promise对象,但是async函数后面可以是 Promise 对象和原始类型的值(跟原始类型值时相当于同步操作)。
  3. 返回值不同
    async函数返回值是Promise对象,Generator函数返回值是Iterator对象。

实现原理:将 Generator 函数和自动执行器,包装在一个函数里

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

async函数内部return语句返回的值,会成为then方法回调函数的参数, 抛出的错误对象会被catch方法回调函数接收到。

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function

上一篇下一篇

猜你喜欢

热点阅读