Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 9 题:Async/Await 如何通过同步的方式实现异步 #156

Open
yygmind opened this issue Jul 8, 2019 · 33 comments
Open

第 9 题:Async/Await 如何通过同步的方式实现异步 #156

yygmind opened this issue Jul 8, 2019 · 33 comments
Labels

Comments

@yygmind
Copy link
Contributor

@yygmind yygmind commented Jul 8, 2019

No description provided.

@chenzesam

This comment has been minimized.

Copy link

@chenzesam chenzesam commented Jul 9, 2019

no body?

@newbeekk

This comment has been minimized.

Copy link

@newbeekk newbeekk commented Jul 9, 2019

where is dalao?

@xzzdll

This comment has been minimized.

Copy link

@xzzdll xzzdll commented Jul 9, 2019

Async/Await 其实是generate函数的语法糖,想搞清楚用同步的方式实现异步只要搞清generate函数内部的机制就好了,不知道对不对- -

@shizhihua666

This comment has been minimized.

Copy link

@shizhihua666 shizhihua666 commented Jul 9, 2019

Async/Await本来就是异步,那有同步可言

@xzzdll

This comment has been minimized.

Copy link

@xzzdll xzzdll commented Jul 9, 2019

@shizhihua666 以同步的方式实现异步,不是说他是同步

@hiblacker

This comment has been minimized.

Copy link

@hiblacker hiblacker commented Jul 9, 2019

这个应该是考察基本使用

function getFoo(){
  return new Promise(resolve => setTimeOut( () => resolve('foo') , 1000))
}
async function asyncFn(){
  let foo = await getFoo()
  let logic = '同步是加引号的'
}
@hanzhibang001

This comment has been minimized.

Copy link

@hanzhibang001 hanzhibang001 commented Jul 9, 2019

Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式。

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

function* gen(){  // 这里的*可以看成 async
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);  // 这里的yield可以看成 await
  console.log(result.bio);
}
var g = gen();
var result = g.next();

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

具体见这里

@YOMXXX

This comment has been minimized.

Copy link

@YOMXXX YOMXXX commented Jul 10, 2019

async await 用于把异步请求变为同步请求的方式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象
例如:这种情况工作中会经常遇到

(async () => {
    var a = await A();
    var b = await B(a);
    var c = await C(b);
    var d = await D(c);
})();

setTimeout 主要用户异步队列的形式,当然其中又分为宏观队列以及微观队列(Promise.then,process.nextTick等),比如隔1000ms执行一段逻辑代码(实际中不一定是1000ms后执行,需要考虑主任务的执行时间)

console.log(1);
setTimeout(() => {
    console.log(2)
}, 0)
setTimeout(() => {
    console.log(3)
}, 1000)
new Promise(resolve => {
    console.log(4)
    resolve()
}).then(() => {
    console.log(5)
})
@nenezsn

This comment has been minimized.

Copy link

@nenezsn nenezsn commented Jul 10, 2019

@shizhihua666 厉害啊 兄弟

@arcsin1

This comment has been minimized.

Copy link

@arcsin1 arcsin1 commented Jul 10, 2019

应该是下面这张写法吧(链式):

Promise.resolve().then(() => {
    console.log(1111)
}).then(() => {
    console.log(2222)
}).then(() => {
    console.log(3333)
})
 //  111, 222,333
@liuliudaren

This comment has been minimized.

Copy link

@liuliudaren liuliudaren commented Jul 11, 2019

where is dalao?

U waiyu good

@cooldrangon

This comment has been minimized.

Copy link

@cooldrangon cooldrangon commented Jul 12, 2019

可以参考你不知道的js一书的中册里面的生成器与迭代器章节看看

@iamwelk

This comment has been minimized.

Copy link

@iamwelk iamwelk commented Jul 13, 2019

题目是不是应该改成“如何通过异步的方式实现同步”?

@cooldrangon

This comment has been minimized.

Copy link

@cooldrangon cooldrangon commented Jul 13, 2019

@lawler61

This comment has been minimized.

Copy link

@lawler61 lawler61 commented Jul 14, 2019

我猜这道题的意思是,async,await 如何通过 同步方式的写法,达到的异步的效果,如

async function a() {
  const b = 1;
  fooFn();
  const res = await barFn(b); // 和同步的 fn 调用没有区别,却可以完成异步的效果
  bazFn(res);
  ...
}

如果是这样的话,那其实就是问 async, await 的实现机制,换句话说就是问 generator 的实现机制,提示一个词 协程

以上一点小见解,可能是错的~

@yang131323

This comment has been minimized.

Copy link

@yang131323 yang131323 commented Jul 15, 2019

是想考查async/await的实现原理吧?我只知道async/await是使用generator+run函数(自动执行generator),但是详细实现原理目前我还未理解,暂时也还没有看到有人对async/await 的实现原理有通俗易懂的讲解,还有很多细节没搞懂,不知道有没有正确理解题意?

@qszy1210

This comment has been minimized.

Copy link

@qszy1210 qszy1210 commented Jul 16, 2019

这个题的意思是不是 async/await 如何通过es5 进行实现? 表述的时候可能有问题吧.
通过es5实现的机制主要是通过 while无线循环 + 状态机 进行实现.

@Mr-jiangzhiguo

This comment has been minimized.

Copy link

@Mr-jiangzhiguo Mr-jiangzhiguo commented Jul 17, 2019

本质是单向链表吧

Async/Await 如何通过同步的方式实现异步

作为前端人员要回答这个问题,需要了解这三个知识点:

  • 同步
  • 异步
  • Async/Await

首先,js 是单线程的(重复三遍),所谓单线程,
通俗的讲就是,一根筋(比喻有点过分,哈哈)执行代码是一行一行的往下走(即所谓的同步),
如果上面的没执行完,就痴痴的等着(是不是很像恋爱中在路边等她/他的你,假装 new 了个对象,啊哈哈哈,调皮一下很开心),
还是举个 🌰 吧:

// chrome 75
function test() {
  let d = Date.now();
  for (let i = 0; i < 1e8; i++) {}
  console.log(Date.now() - d); // 62ms左右
}
function test1() {
  let d = Date.now();

  console.log(Date.now() - d); // 0
}
test();
test1();

上面仅仅是一个 for 循环,而在实际应用中,会有大量的网络请求,它的响应时间是不确定的,这种情况下也要痴痴的等么?显然是不行的,因而 js 设计了异步,即 发起网络请求(诸如 IO 操作,定时器),由于需要等服务器响应,就先不理会,而是去做其他的事儿,等请求返回了结果的时候再说(即异步)。
那么如何实现异步呢?其实我们平时已经在大量使用了,那就是 callback,例如:

$.ajax({
  url: 'http://xxx',
  success: function(res) {
    console.log(res);
  },
});

success 作为函数传递过去并不会立即执行,而是等请求成功了才执行,即回调函数(callback)

const fs = require('fs');
fs.rename('旧文件.txt', '新文件.txt', err => {
  if (err) throw err;
  console.log('重命名完成');
});

和网络请求类似,等到 IO 操作有了结果(无论成功与否)才会执行第三个参数:(err)=>{}

从上面我们就可以看出,实现异步的核心就是回调钩子,将 cb 作为参数传递给异步执行函数,当有了结果后在触发 cb。想了解更多,去看看 event-loop 机制吧。

至于 async/await 是如何出现的呢,在 es6 之前,大多 js 数项目中会有类似这样的代码:

ajax1(url, () => {
  ajax2(url, () => {
    ajax3(url, () => {
      // do something
    });
  });
});

这种函数嵌套,大量的回调函数,使代码阅读起来晦涩难懂,不直观,形象的称之为回调地狱(callback hell),所以为了在写法上能更通俗一点,es6+陆续出现了 PromiseGeneratorAsync/await,力求在写法上简洁明了,可读性强。

=========================我是分割线==========================

以上只是铺垫,下面在进入正题 👇,开始说道说道主角:async/await

=========================我是分割线==========================

async/await 是参照 Generator 封装的一套异步处理方案,可以理解为 Generator 的语法糖,

所以了解 async/await 就不得不讲一讲 Generator,

Generator 又依赖于迭代器Iterator

所以就得先讲一讲 Iterator,

Iterator 的思想呢又来源于单向链表,

终于找到源头了:单向链表

1. 单向链表

wiki:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序储存数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序储存,链表在插入的时候可以达到 o(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 o(n)的时间,而顺序表响应的时间复杂度分别是 o(logn)和 o(1)。

总结一下链表优点:

  • 无需预先分配内存
  • 插入/删除节点不影响其他节点,效率高(典型的例子:git commit、dom 操作

单向链表:是链表中最简单的一种,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
image
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接

单链特点:节点的链接方向是单向的;相对于数组来说,单链表的的随机访问速度较慢,但是单链表删除/添加数据的效率很高。

理解 js 原型链/作用域链的话,理解这个很容易,他们是相通的。编程语言中,数组的长度时固定的,所以数组中的增加和删除比较麻烦,需要频繁的移动数组中的其他元素,而 js 作为一门动态语言,数组本质是一个类似数组的对象,是动态的,不需要预先分配内存

那么如何设计一个单向链表呢?这个取决于我们需要哪些操作,通常有:

  • append(element):追加节点
  • insert(element,index):在索引位置插入节点
  • remove(element):删除第一个匹配到的节点
  • removeAt(index):删除指定索引节点
  • removeAll(element):删除所有匹配的节点
  • get(index):获取指定索引的节点信息
  • set(element,index):修改指定索引的节点值
  • indexOf(element):获取某节点的索引位置
  • clear():清除所有节点
  • length():返回节点长度
  • printf():打印节点信息

看到这些方法是不是有些许熟悉,当你用原生 js 或 jq 时常会用上面类似的方法,现在根据上面列出的方法进行实现一个单向链:

// 节点模型
class LinkNode {
  constructor(element, next) {
    this.element = element;
    this.next = next;
  }
}

class LinkedList {
  constructor() {
    this._head = null;
    this._size = 0;
    this._errorBoundary = this._errorBoundary.bind(this);
    this._getNodeByIndex = this._getNodeByIndex.bind(this);
    this.append = this.append.bind(this);
    this.insert = this.insert.bind(this);
    this.remove = this.remove.bind(this);
    this.removeAt = this.removeAt.bind(this);
    this.removeAll = this.removeAll.bind(this);
    this.getElement = this.getElement.bind(this);
    this.setIndex = this.setIndex.bind(this);
    this.indexOf = this.indexOf.bind(this);
    this.clear = this.clear.bind(this);
    this.length = this.length.bind(this);
    this.printf = this.printf.bind(this);
  }

  // 边界检验
  _errorBoundary(index) {
    if (index < 0 || index >= this._size) {
      throw `超出边界(${0}~${this._size}),目标位置${index}不存在!`;
    }
  }
  // 根据索引获取目标对象
  _getNodeByIndex(index) {
    this._errorBoundary(index);
    let obj = this._head;
    for (let i = 0; i < index; i++) {
      obj = obj.next;
    }
    return obj;
  }
  // 追加节点
  append(element) {
    if (this._size === 0) {
      this._head = new LinkNode(element, null);
    } else {
      let obj = this._getNodeByIndex(this._size - 1);
      obj.next = new LinkNode(element, null);
    }
    this._size++;
  }
  // 在索引位置插入节点
  insert(element, index) {
    if (index === 0) {
      this._head = new LinkNode(element, this._head);
    } else {
      let obj = this._getNodeByIndex(index - 1);
      obj.next = new LinkNode(element, obj.next);
    }
    this._size++;
  }
  // 删除第一个匹配到的节点
  remove(element) {
    if (this._size < 1) return null;

    if (this._head.element == element) {
      this._head.element = this._head.next;
      this._size--;
      return element;
    } else {
      let temp = this._head;
      while (temp.next) {
        if (temp.next.element == element) {
          temp.next = temp.next.next;
          this._size--;
          return element;
        } else {
          temp = temp.next;
        }
      }
    }
    return null;
  }
  // 删除指定索引节点
  removeAt(index) {
    this._errorBoundary(index);
    let element = null;
    if (index === 0) {
      element = this._head.element;
      this._head = this._head.next;
    } else {
      let prev = this._getNodeByIndex(index - 1);
      element = prev.next.element;
      prev.next = prev.next.next;
    }
    this._size--;
    return element;
  }
  // 删除所有匹配的节点
  removeAll(element) {
    // 创建虚拟头节点,
    let v_head = new LinkNode(null, this._head);
    let tempNode = v_head;
    // let tempEle = null;
    while (tempNode.next) {
      if (tempNode.next.element == element) {
        tempNode.next = tempNode.next.next;
        this._size--;
        // tempEle = element;
      } else {
        tempNode = tempNode.next;
      }
    }
    this._head = v_head.next;
  }
  // 获取指定索引的节点信息
  getElement(index) {
    return this._getNodeByIndex(index).element;
  }
  // 修改指定索引的节点值
  setIndex(element, index) {
    this._errorBoundary(index);
    let obj = this._getNodeByIndex(index);
    obj.element = element;
  }
  // 获取某节点的索引位置
  indexOf(element) {
    let obj = this._head;
    let index = -1;
    for (let i = 0; i < this._size; i++) {
      if (obj.element == element) {
        index = i;
        break;
      }
      obj = obj.next;
    }
    return index;
  }
  // 清除所有节点
  clear() {
    this._head = null;
    this._size = 0;
  }
  // 返回节点长度
  length() {
    return this._size;
  }
  // 打印节点信息
  printf() {
    let obj = this._head;
    const arr = [];
    while (obj != null) {
      arr.push(obj.element);
      obj = obj.next;
    }
    const str = arr.join('->');
    return str || null;
  }
}

const obj = new LinkedList();
obj.append(0);
obj.append(1);
obj.append(2);
obj.printf();
// "0->1->2"

obj.insert(3, 3);
obj.printf();
// "0->1->2->3"

obj.remove(3);
obj.printf();
// "0->1->2"

obj.removeAt(0);
obj.printf();
// "1->2"

obj.setIndex(0, 0);
obj.printf();
// "0->2"

obj.indexOf(2);
// 1

obj.length();
// 2

obj.clear();
obj.printf();
// null

查看源码

通过以上,我假装你明白什么是单向链表,并且能够用代码实现一个单向链表了,下一步开始说一说迭代器 Iterator

2. Iterator

Iterator 翻译过来就是**迭代器(遍历器)**让我们先来看看它的遍历过程(类似于单向链表):

  • 创建一个指针对象,指向当前数据结构的起始位置
  • 第一次调用指针对象的 next 方法,将指针指向数据结构的第一个成员
  • 第二次调用指针对象的 next 方法,将指针指向数据结构的第二个成员
  • 不断的调用指针对象的 next 方法,直到它指向数据结构的结束位置

一个对象要变成可迭代的,必须实现 @@iterator 方法,即对象(或它原型链上的某个对象)必须有一个名字是 Symbol.iterator 的属性(原生具有该属性的有:字符串、数组、类数组的对象、Set 和 Map):

属性
[Symbol.iterator]: 返回一个对象的无参函数,被返回对象符合迭代器协议

当一个对象需要被迭代的时候(比如开始用于一个 for..of 循环中),它的 @@iterator 方法被调用并且无参数,然后返回一个用于在迭代中获得值的迭代器

迭代器协议:产生一个有限或无限序列的值,并且当所有的值都已经被迭代后,就会有一个默认的返回值

当一个对象只有满足下述条件才会被认为是一个迭代器:

它实现了一个 next() 的方法,该方法必须返回一个对象,对象有两个必要的属性:

  • done(bool)
    • true:迭代器已经超过了可迭代次数。这种情况下,value 的值可以被省略
    • 如果迭代器可以产生序列中的下一个值,则为 false。这等效于没有指定 done 这个属性
  • value 迭代器返回的任何 JavaScript 值。done 为 true 时可省略

根据上面的规则,咱们来自定义一个简单的迭代器:

const makeIterator = arr => {
  let nextIndex = 0;
  return {
    next: () =>
      nextIndex < arr.length
        ? { value: arr[nextIndex++], done: false }
        : { value: undefined, done: true },
  };
};
const it = makeIterator(['人月', '神话']);
console.log(it.next()); // { value: "人月", done: false }
console.log(it.next()); // { value: "神话", done: false }
console.log(it.next()); // {value: undefined, done: true }

我们还可以自定义一个可迭代对象:

const myIterable = {};
myIterable[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
};

for (let value of myIterable) {
  console.log(value);
}
// 1
// 2
// 3

//or

console.log([...myIterable]); // [1, 2, 3]

了解了迭代器,下面可以进一步了解生成器了

3. Generator

Generator:生成器对象是生成器函数(GeneratorFunction)返回的,它符合可迭代协议迭代器协议,既是迭代器也是可迭代对象,可以调用 next 方法,但它不是函数,更不是构造函数

生成器函数(GeneratorFunction):

function* name([param[, param[, ... param]]]) { statements }

  • name:函数名
  • param:参数
  • statements:js 语句

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器对象,当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现 yield 的位置为止(让执行处于暂停状),yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行),调用 next() (再启动)方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值,例如:

function* another() {
  yield '人月神话';
}
function* gen() {
  yield* another(); // 移交执行权
  const a = yield 'hello';
  const b = yield a; // a='world' 是 next('world') 传参赋值给了上一个 yidle 'hello' 的左值
  yield b; // b=! 是 next('!') 传参赋值给了上一个 yidle a 的左值
}
const g = gen();
g.next(); // {value: "人月神话", done: false}
g.next(); // {value: "hello", done: false}
g.next('world'); // {value: "world", done: false} 将 'world' 赋给上一条 yield 'hello' 的左值,即执行 a='world',
g.next('!'); // {value: "!", done: false} 将 '!' 赋给上一条 yield a 的左值,即执行 b='!',返回 b
g.next(); // {value: undefined, done: false}

看到这里,你可能会问,Generatorcallback 有啥关系,如何处理异步呢?其实二者没有任何关系,我们只是通过一些方式强行的它们产生了关系,才会有 Generator 处理异步

我们来总结一下 Generator 的本质,暂停,它会让程序执行到指定位置先暂停(yield),然后再启动(next),再暂停(yield),再启动(next),而这个暂停就很容易让它和异步操作产生联系,因为我们在处理异步时:开始异步处理(网络求情、IO 操作),然后暂停一下,等处理完了,再该干嘛干嘛。不过值得注意的是,js 是单线程的(又重复了三遍),异步还是异步,callback 还是 callback,不会因为 Generator 而有任何改变

下面来看看,用 Generator 实现异步:

const promisify = require('util').promisify;
const path = require('path');
const fs = require('fs');
const readFile = promisify(fs.readFile);

const gen = function*() {
  const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
  console.log(res1);
  const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
  console.log(res2);
};

const g = gen();

const g1 = g.next();
console.log('g1:', g1);

g1.value
  .then(res1 => {
    console.log('res1:', res1);
    const g2 = g.next(res1);
    console.log('g2:', g2);
    g2.value
      .then(res2 => {
        console.log('res2:', res2);
        g.next(res2);
      })
      .catch(err2 => {
        console.log(err2);
      });
  })
  .catch(err1 => {
    console.log(err1);
  });
// g1: { value: Promise { <pending> }, done: false }
// res1: {
//   "a": 1
// }

// {
//   "a": 1
// }

// g2: { value: Promise { <pending> }, done: false }
// res2: {
//   "b": 2
// }

// {
//   "b": 2
// }

以上代码是 Generatorcallback 结合实现的异步,可以看到,仍然需要手动执行 .then 层层添加回调,但由于 next() 方法返回对象 {value: xxx,done: true/false} 所以我们可以简化它,写一个自动执行器:

const promisify = require('util').promisify;
const path = require('path');
const fs = require('fs');
const readFile = promisify(fs.readFile);

function run(gen) {
  const g = gen();
  function next(data) {
    const res = g.next(data);
    // 深度递归,只要 `Generator` 函数还没执行到最后一步,`next` 函数就调用自身
    if (res.done) return res.value;
    res.value.then(function(data) {
      next(data);
    });
  }
  next();
}
run(function*() {
  const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
  console.log(res1);
  // {
  //   "a": 1
  // }
  const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
  console.log(res2);
  // {
  //   "b": 2
  // }
});

说了这么多,怎么还没有到 async/await,客官别急,马上来了(其实我已经漏了一些内容没说:Promise 和 callback 的关系,thunk 函数,co 库,感兴趣的可以去 google 一下,ruanyifeng 老师讲的es6 入门非常棒,我时不时的都会去看一看)

4. Async/Await

首先,async/awaitGenerator 的语法糖,上面我是分割线下的第一句已经讲过,先来看一下二者的对比:

// Generator
run(function*() {
  const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
  console.log(res1);
  const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
  console.log(res2);
});

// async/await
const readFile = async ()=>{
  const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
  console.log(res1);
  const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
  console.log(res2);
  return 'done';
}
const res = readFile();

可以看到,async function 代替了 function*await 代替了 yield,同时也无需自己手写一个自动执行器 run

现在再来看看async/await 的特点:

  • await 后面跟的是 Promise 对象时,才会异步执行,其它类型的数据会同步执行
  • 执行 const res = readFile(); 返回的仍然是个 Promise 对象,上面代码中的 return 'done'; 会直接被下面 then 函数接收到
res.then(data => {
  console.log(data); // done
});

啊,终于完了,一个 async-await 连带出来这么多知识点,以后面试被问到它的原理时,希望能够帮助到你

【参考】:

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%8D%8F%E8%AE%AE
  2. http://es6.ruanyifeng.com/#docs/iterator
  3. http://es6.ruanyifeng.com/#docs/async

===🧐🧐 文中不足,欢迎指正 🤪🤪===

原文地址:https://juejin.im/post/5d2c814c6fb9a07ecd3d8e43

@hohenheimsd

This comment has been minimized.

Copy link

@hohenheimsd hohenheimsd commented Jul 17, 2019

function get(val) {
  return new Promise((resolve, reject)=>{
    console.log(`正在加载${val}`);
    setTimeout(()=>{
      resolve(val);
      console.log(`${val}加载完毕`);
    },5000 + Math.random() * 10000);
  });
}

function * g() {
  var five = yield get(5);
  var seven = yield get(7);
  var eleven = yield get(11);
  return five + seven + eleven;
}

function run(g) {
  return new Promise((resolve, reject)=>{
    var iterator = g();
    var generated;
    start();
    function start(value) {
      generated = iterator.next(value);
      if(!generated.done){
        generated.value.then(data=>{
          start(data);
        });
      }else {
        resolve(generated.value);
      }
    }
  });
}

run(g).then(console.log);
@zzNire

This comment has been minimized.

Copy link

@zzNire zzNire commented Aug 1, 2019

async awiat 是一种语法糖,基于Generator 函数和自动执行器实现

function getData(){
    return new Promise(resolve=>{
        setTimeout(() => {
            console.log('done');
            resolve();
        }, 1000);
    })
}

function print(){
    console.log('print');
}

//async await 函数
function downloading(){
    function * loadingData(){ //Generator 函数
        var x1 = yield getData();
        var x2 = yield print();
        return 1;
    }
    function start(fn){ //自动执行器实现
        return new Promise((resolve,reject)=>{
            var it = fn();
            function run(value){
                var result = it.next(value);
                if(result.done){
                    resolve(result.value);
                    return;
                }
                Promise.resolve(result.value).then(data=>{
                    run(data);
                })
            }
            run();
        })
        
    }
    return start(loadingData);
}
downloading().then(v=>{console.log(v)})
@yaodongyi

This comment has been minimized.

Copy link

@yaodongyi yaodongyi commented Aug 5, 2019

async function fun1() {
  await console.log(1);
  await console.log(2);
}
async function fun2() {
  await console.log(3);
  await console.log(4);
}
// async 实现的是一个异步操作,await 等待一个异步方法执行完成。
// async内使用await等待async的执行完成,就形成了异步函数的同步实现。
async function fun3() {
  await fun1();
  await console.log(5);
  await fun2();
}
fun3();
@Buzz888

This comment has been minimized.

Copy link

@Buzz888 Buzz888 commented Sep 10, 2019

Async/Await 是函数Generator的语法糖.
Generator之所以可以通过同步实现异步是它具有暂停执行和恢复执行的特性和函数体内外的数据交换和错误处理机制。

@yft

This comment has been minimized.

Copy link

@yft yft commented Sep 26, 2019

@Mr-jiangzhiguo 您好,请教一下,为什么在 LinkedList 中要分别为每个方法绑定一次 this 呢?


一个猜想,是为了这样使用的时候依旧能够正确访问到 this 吗?

class Test {
    constructor() {
        this.a = this.a.bind(this)
    }
    a() {
        console.log(this);
    }
}

let t = new Test()
let b = t.a;
b();
@Mr-jiangzhiguo

This comment has been minimized.

Copy link

@Mr-jiangzhiguo Mr-jiangzhiguo commented Sep 26, 2019

@Mr-jiangzhiguo 您好,请教一下,为什么在 LinkedList 中要分别为每个方法绑定一次 this 呢?

一个猜想,是为了这样使用的时候依旧能够正确访问到 this 吗?

class Test {
    constructor() {
        this.a = this.a.bind(this)
    }
    a() {
        console.log(this);
    }
}

let t = new Test()
let b = t.a;
b();

首先,这是个细节,你看的很仔细,问题也问得不错,

你的猜想不正确,绑定与否,都可以正确访问到this,

区别就是,在构造器里面绑定后(用箭头函数也是可以的,看Test2),实例化的时候就能显式的看到有哪些属性,给你举个例子:

class Test {
    constructor() {
        this.a = this.a.bind(this)
    }
    a() {
        console.log(this);
    }
}
class Test1 {
    a() {
        console.log(this);
    }
}
class Test2 {
    a=() =>{
        console.log(this);
    }
}
let t = new Test();
let t1 = new Test1();
let t2 = new Test2();

// 然后打印,你就会看出来区别
console.log(t,t1,t2)
console.log('======')
// 都可以调用`a`方法
t.a();
t1.a();
t2.a();

image

@yft

This comment has been minimized.

Copy link

@yft yft commented Sep 26, 2019

@Mr-jiangzhiguo 原来是这样,明白了,谢谢解惑😄

@bibi7 bibi7 mentioned this issue Oct 10, 2019
@MyObjects

This comment has been minimized.

Copy link

@MyObjects MyObjects commented Nov 12, 2019

function ajax2(){
    return new Promise(resolve=>{
        setTimeout(()=>{
            console.log(111)
            resolve('2秒后')
        },2000)
    })
}

async function indexasync(){
    console.log(0)
    let result = await ajax2();//2秒后
    console.log(2000)
    return '结束了'
}

indexasync().then(res=>{
    console.log(res);//输出结束了
}).catch(error=>console.log(error))

//结果
0
//2s后
111
2000
结束了
@asmallgod

This comment has been minimized.

Copy link

@asmallgod asmallgod commented Dec 12, 2019

可以参考你不知道的js一书的中册里面的生成器与迭代器章节看看

赞同,那一部分我看了3遍,真的很精彩。就是generator和Promise的语法糖,利用generator可暂停机制和消息传递机制实现的。太nb了。

@yygmind yygmind added the 异步 label Dec 16, 2019
@Hideer

This comment has been minimized.

Copy link

@Hideer Hideer commented Dec 24, 2019

image
字打错了...

@Mr-jiangzhiguo

This comment has been minimized.

Copy link

@Mr-jiangzhiguo Mr-jiangzhiguo commented Dec 24, 2019

image
字打错了...

😅,已更正

@ZangYuSong

This comment has been minimized.

Copy link

@ZangYuSong ZangYuSong commented Jan 3, 2020

其实本质上还是异步的,只是把每次异步后续的执行放到了对应的promise.then 中执行。

类似于下方的执行方式:

async function test1() {
  const a = await new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
  const b = 2;
  const c = await new Promise(resolve => {
    setTimeout(() => {
      resolve(3);
    }, 1000);
  });
  const d = 4;
  console.log(a + b + c + d);
}

function test2() {
  var a, b, c, d;
  new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  }).then(data => {
    a = data;
    b = 2;
    new Promise(resolve => {
      setTimeout(() => {
        resolve(3);
      }, 1000);
    }).then(data => {
      c = data;
      d = 4;
      console.log(a + b + c + d);
    });
  });
}

不同的是,最终转换为 自执行迭代器的方式来实现。类似下边的:(自己写的假代码没有自执行)

function* gen() {
  yield new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
  yield 2;
  yield new Promise(resolve => {
    setTimeout(() => {
      resolve(3);
    }, 1000);
  });
  yield 4;
}

function test3() {
  var a, b, c, d;
  var iterator = gen(); 
  a = iterator.next().value;
  b = iterator.next().value;
  c = iterator.next().value;
  d = iterator.next().value;
  console.log(a + b + c + d);
}

这是 babel 转码之后的:

var test1 = (function() {
  var _ref = _asyncToGenerator(
    /*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
      var a, b, c, d;
      return regeneratorRuntime.wrap(
        function _callee$(_context) {
          while (1) {
            switch ((_context.prev = _context.next)) {
              case 0:
                _context.next = 2;
                return new Promise(function(resolve) {
                  setTimeout(function() {
                    resolve(1);
                  }, 1000);
                });

              case 2:
                a = _context.sent;
                b = 2;
                _context.next = 6;
                return new Promise(function(resolve) {
                  setTimeout(function() {
                    resolve(3);
                  }, 1000);
                });

              case 6:
                c = _context.sent;
                d = 4;

                console.log(a + b + c + d);

              case 9:
              case "end":
                return _context.stop();
            }
          }
        },
        _callee,
        this
      );
    })
  );

  return function test1() {
    return _ref.apply(this, arguments);
  };
})();

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}
@zhangminggeek

This comment has been minimized.

Copy link

@zhangminggeek zhangminggeek commented Jan 4, 2020

// 删除指定索引节点
  removeAt(index) {
    this._errorBoundary(index);
    let element = null;
    if (index === 0) {
      element = this._head.element;
      this._head = this._head.next;
    } else {
      let prev = this._getNodeByIndex(index - 1);
      element = prev.next.element;
      prev.next = prev.next.next;
    }
    this._size--;
    return element;
  }

请教一下,这里为什么不直接获取 index 所在的节点而要获取 prev,写成以下方式可以吗

// 删除指定索引节点
  removeAt(index) {
    this._errorBoundary(index);
    let element = null;
    if (index === 0) {
      element = this._head.element;
      this._head = this._head.next;
    } else {
      let node = this._getNodeByIndex(index);
      element = node.element;
      prev.next = node.next;
    }
    this._size--;
    return element;
  }
@Mr-jiangzhiguo

This comment has been minimized.

Copy link

@Mr-jiangzhiguo Mr-jiangzhiguo commented Jan 6, 2020

// 删除指定索引节点
  removeAt(index) {
    this._errorBoundary(index);
    let element = null;
    if (index === 0) {
      element = this._head.element;
      this._head = this._head.next;
    } else {
      let prev = this._getNodeByIndex(index - 1);
      element = prev.next.element;
      prev.next = prev.next.next;
    }
    this._size--;
    return element;
  }

请教一下,这里为什么不直接获取 index 所在的节点而要获取 prev,写成以下方式可以吗

// 删除指定索引节点
  removeAt(index) {
    this._errorBoundary(index);
    let element = null;
    if (index === 0) {
      element = this._head.element;
      this._head = this._head.next;
    } else {
      let node = this._getNodeByIndex(index);
      element = node.element;
      prev.next = node.next;
    }
    this._size--;
    return element;
  }

除了首尾,每个节点都是前后关联的,它来自前一个节点,指向后一个节点,

其实删除本质就是将它前一个节点的指针,指向它下一个节点。也就是说把当前节点从这个链中剥离

@JiangMengLei

This comment has been minimized.

Copy link

@JiangMengLei JiangMengLei commented Jan 11, 2020

题目是不是应该改成“如何通过异步的方式实现同步”?

不是吧,本质还是异步,只不过写法上看起来是同步,所以是 用同步写法(async/await)实现了异步

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.