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 · 51 comments
Open

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

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

Comments

@yygmind
Copy link
Contributor

yygmind commented Jul 8, 2019

No description provided.

@chenzesam
Copy link

no body?

@wdzyy
Copy link

wdzyy commented Jul 9, 2019

where is dalao?

@xzzdll
Copy link

xzzdll commented Jul 9, 2019

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

@shizhihua666
Copy link

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

@xzzdll
Copy link

xzzdll commented Jul 9, 2019

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

@hiblacker
Copy link

hiblacker commented Jul 9, 2019

这个应该是考察基本使用

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

@NathanHan1
Copy link

NathanHan1 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
Copy link

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
Copy link

nenezsn commented Jul 10, 2019

@shizhihua666 厉害啊 兄弟

@arcsin1
Copy link

arcsin1 commented Jul 10, 2019

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

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

@liuliudaren
Copy link

where is dalao?

U waiyu good

@cooldrangon
Copy link

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

@iamwelk
Copy link

iamwelk commented Jul 13, 2019

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

@cooldrangon
Copy link

cooldrangon commented Jul 13, 2019 via email

@liam61
Copy link

liam61 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
Copy link

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

@qszy1210
Copy link

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

@daolou
Copy link

daolou commented Jul 17, 2019

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

首先想要更好的理解 Async/Await,需要了解这两个知识点:

  • 同步
  • 异步

背景

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

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

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)

// IO操作
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, () => {
  // do something 1
  ajax2(url, () => {
    // do something 2
    ajax3(url, () => {
      // do something 3
      // ...
    });
  });
});

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

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

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

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

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

async-await

所以了解 async/await 就不得不讲一讲 Generator(首次将协程的概念引入 js,是协程的子集,不过由于不能指定让步的协程,只能让步给生成器(迭代器)的调用者,所以也称为非对称协程),

Generator 又返回迭代器Iterator对象,

所以就得先讲一讲 Iterator,

IteratorGenerator 都属于协程,

终于找到源头了:协程

协程

wiki:协程(英语:coroutine)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道

协程可以通过 yield(取其“让步”之义而非“出产”)来调用其它协程,接下来的每次协程被调用时,从协程上次 yield 返回的位置接着执行,通过 yield 方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的

协程是追求极限性能和优美的代码结构的产物
协程间的调用是逻辑上可控的,时序上确定的

协程是一种比线程更加轻量级的存在,是语言层级的构造,可看作一种形式的控制流,在内存间执行,无像线程间切换的开销。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。

协程概念的提出比较早,单核CPU场景中发展出来的概念,通过提供挂起恢复接口,实现在单个CPU上交叉处理多个任务的并发功能。

那么本质上就是在一个线程的基础上,增加了不同任务栈的切换,通过不同任务栈的挂起和恢复,线程中进行交替运行的代码片段,实现并发的功能。

其实从这里可以看出 「协程间的调用是逻辑上可控的,时序上确定的」

那么如何理解 js 中的协程呢?

  • js 公路只是单行道(主线程),但是有很多车道(辅助线程)都可以汇入车流(异步任务完成后回调进入主线程的任务队列)
  • generator 把 js 公路变成了多车道(协程实现),但是同一时间只有一个车道上的车能开(依然单线程),不过可以自由变道(移交控制权)

协程实现

这里是一个简单的例子证明协程的实用性。假设这样一种生产者-消费者的关系,一个协程生产产品并将它们加入队列,另一个协程从队列中取出产品并消费它们。伪码表示如下:

var q := 新建队列

coroutine 生产者
  loop
    while q 不满载
      建立某些新产品
      向 q 增加这些产品
    yield 给消费者

coroutine 消费者
  loop
    while q 不空载
      从 q 移除某些产品
      使用这些产品
    yield 给生产者

v8 实现源码:js-generatorruntime-generator

编译模拟实现(es5):regenerator

通过以上,我假装你明白什么是协程,下一步开始说一说迭代器 Iterator

Iterator

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

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

一个对象要变成可迭代的,必须实现 @@iterator 方法,即对象(或它原型链上的某个对象)必须有一个名字是 Symbol.iterator 的属性(原生具有该属性的有:StringArrayTypedArrayMapSet)可通过常量 Symbol.iterator 访问:

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

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

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

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

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

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

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

const getRawType = (target) => Object.prototype.toString.call(target).slice(8,-1);

const __createArrayIterable = (arr) => {
  if (typeof Symbol !== 'function' || !Symbol.iterator) return {};
  if(getRawType(arr) !== 'Array') throw new Error('it must be Array');
  const iterable = {};
  iterable[Symbol.iterator] = () => {
    arr.length++;
    const iterator = {
      next: () => ({ value: arr.shift(), done: arr.length <= 0 })
    }
    return iterator;
  };
  return iterable;
};

const itable = __createArrayIterable(['人月',  '神话']);
const it = itable[Symbol.iterator]();

console.log(it.next()); // { value: "人月", done: false }
console.log(it.next()); // { value: "神话", done: false }
console.log(it.next()); // {value: undefined, done: true }

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

Object.prototype[Symbol.iterator] = function () {
  const items = Object.entries(this);
  items.length++;
  return {
    next: () => ({ value: items.shift(), done: items.length <= 0 })
  }
}
// or
Object.prototype[Symbol.iterator] = function* () {
  const items = Object.entries(this);
  for (const item of items) {
    yield item;
  }
}
const obj = { name: 'amap', bu: 'sharetrip'}
for (let value of obj) {
  console.log(value);
}
// ["name", "amap"]
// ["bu", "sharetrip"]
// or
console.log([...obj]); // [["name", "amap"], ["bu", "sharetrip"]]

💡 除了 for map forEach 等方法如何遍历一个数组?

参考答案

const getIterator = (iteratorable) => iteratorable[Symbol.iterator]();
const arr = [0,1,2,3,4,5];
const iterator = getIterator(arr);
while(true){
  const obj = iterator.next();
  if(obj.done){
    break;
  }
  console.log(obj.value);
}

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

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 + Promise 写一段异步代码:

const gen = function*() {
  const res1 = yield Promise.resolve({a: 1});
  const res2 = yield Promise.resolve({b: 2});
};

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 }
// g2: { value: Promise { <pending> }, done: false }
// res2: { "b": 2 }

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

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 Promise.resolve({a: 1});
  console.log(res1);
  // { "a": 1 }
  const res2 = yield Promise.resolve({b: 2});
  console.log(res2);
  // { "b": 2 }
});

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

💡 分析下面 log 输出什么内容?

function* gen() {
  const ask1 = yield "2 + 2 = ?";
  console.log(ask1);

  const ask2 = yield "3 * 3 = ?"
  console.log(ask2);
}

const generator = gen();

console.log( generator.next().value );

console.log( generator.next(4).value );

console.log( generator.next(9).done );
参考答案

// 2 + 2 = ?
// 4
// 3 + 3 = ?
// 6
// true

Async/Await

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

// Generator
run(function*() {
  const res1 = yield Promise.resolve({a: 1});
  console.log(res1);

  const res2 = yield Promise.resolve({b: 2});
  console.log(res2);
});

// async/await
const aa = async ()=>{
  const res1 = await Promise.resolve({a: 1});
  console.log(res1);

  const res2 = await Promise.resolve({b: 2});
  console.log(res2);

  return 'done'
}
const res = aa();

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

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

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

最后咱们来总结一下:

优点:

  • 内置执行器:自带执行器
  • 更好的语义:比起星号和 yield,语义更清楚了
  • 更广的适用性:await 命令后面,可以跟 Promise 对象和原始类型的值(这时等同于同步操作)

注意点:

  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发(Promise.all
  • 再循环中需注意它的使用,尽量在 for/for..of(迭代遍历器) 中使用,永远不要在 forEach/filter 中使用,也尽量不要在 map 中使用
  • 兼容性(caniusenode.green)不太好,当然一般情况下,可以借助编译工具来进行 polyfill(babel)或 es6-shim(转换后即语法糖实现的协程效率低,co + generatorcb 的方式性能差)
  • 可以在生命周期函数中使用,在线例子: ReactVue
  • 错误捕获:需要捕获多个错误并做不同的处理时,可以考虑给 await 后的 promise 对象添加 catch 函数,为此我们需要写一个 helper:
// to.js
export default function to(promise) {
  return promise.then(data => {
    return [null, data];
  })
  .catch(err => [err]);
}

/***使用***/
import to from './to';

async function asyncTask() {

  const [err1, res1] = await to(fn1);
  if(!res1) throw new CustomerError('No res1 found');

  const [err2, res2] = await to(fn2);
  if(err) throw new CustomError('Error occurred while task2');
}

💡 给定一个 URL 数组,如何实现接口的继发和并发?

参考答案

// 继发一
async function loadData() {
  var res1 = await fetch(url1);
  var res2 = await fetch(url2);
  var res3 = await fetch(url3);
  return "when all done";
}
// 继发二
async function loadData(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
/********/
// 并发一
async function loadData() {
  var res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)]);
  return "when all done";
}
// 并发二
async function loadData(urls) {
  // 并发读取 url
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });
  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

啊,终于完了,一个 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.cn/post/6844903891021086734

@hohenheimsd
Copy link

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
Copy link

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
Copy link

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
Copy link

Buzz888 commented Sep 10, 2019

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

@yft
Copy link

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();

@daolou
Copy link

daolou 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
Copy link

yft commented Sep 26, 2019

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

@MyObjects
Copy link

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
Copy link

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

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

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

Hideer commented Dec 24, 2019

image
字打错了...

@daolou
Copy link

daolou commented Dec 24, 2019

image
字打错了...

😅,已更正

@ZangYuSong
Copy link

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
Copy link

// 删除指定索引节点
  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;
  }

@daolou
Copy link

daolou 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
Copy link

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

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

@taoeer
Copy link

taoeer commented Mar 25, 2020

我觉得把题目改成 怎么用同步的方式实现异步 会更好!

@Gumplefik
Copy link

这问题看的有点莫名其妙,可能需要改成 async/await是如何实现的会好一些

@YuArtian
Copy link

v8 来讲的话,难道不是通过协程实现的么,外加 promise
async 和 Generator 都是用到了协程,但是作用不太一样吧
babel 把async转了 generators 和 promises,再用 regenerator-runtime 把 generator 转换为 ES5 代码
语法糖的事情似乎是标准文档上说的?问题是。。文档上的东西,都只是说说而已,真正怎么实现还是引擎说了算

@Fan-zexu
Copy link

Fan-zexu commented Jul 2, 2020

�v8底层协程的概念,来控制异步和同步的执行权的管理。而为了兼容浏览器差异,才有了类似regenerator这样的库通过swich case 的方式来模拟协程。

@gooqiao
Copy link

gooqiao commented Aug 4, 2020

async/await 就是一个自执行的Generator, 另外返回值一定是Promise.

@fanzye95th
Copy link

where is dalao?you are yincai

@getatny
Copy link

getatny commented Sep 7, 2020

老实说,await如何通过同步的方式实现异步不是因为await之后的所有语句都会被放到微任务队列吗?个人觉得解释async/await的时间原理什么的肯定是更好,但是他为什么“同步”了,就是因为他在执行上把await之后的语句都“异步”了,保证了await之后的语句都在await的回调之后执行吧。(小白乱说的)

@mengshis
Copy link

async基础技术使用了生成器和promise,生成器是协程的实现,利用生成器实现生成器函数的暂停和恢复。
async是通过异步调用并隐式返回promise所为结果的函数。
await会默认创建一个promise对象。

@flftfqwxf
Copy link

我想问问,这个题目是要解决的是什么问题?

@AKclown
Copy link

AKclown commented Dec 26, 2020

this._head.element = this._head.next;

  // 删除第一个匹配到的节点
  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;
  }

this._head.element = this._head.next; 是不是应该改为 this._head = this._head.next; 才对呢

@passing-j
Copy link

passing-j commented Jan 5, 2021

async/await 组合可以像写同步代码那样写异步代码。比如有 3 个 ajax 请求,每个请求都依赖上一个的结果,可以写成:

function st(time, value) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('等待%d秒', time/1000)
            value && console.log(value)
            resolve()
        }, time)
    })
}
async function syn() {
    await st(3000)
    await st(1000, '拿到步骤1的结果')
    await st(2000, '拿到步骤2的结果')
}
syn()

@wensiyuanseven
Copy link

       const readFile = function (fileName) {
            return new Promise(function (resolve, reject) {
                setTimeout(() => {
                    resolve(fileName)
                }, 1000)
            });
        };

        const gen = function* () {
            const f1 = yield readFile('/etc/fstab');
            const f2 = yield readFile('/etc/shells');
            console.log(f1);
            console.log(f2);
        };
        // 手动执行代码
        // var g = gen();
        // g.next().value.then(function (data) {
        //     g.next(data).value.then(function (data) {
        //         g.next(data);
        //     });
        // });

        // 自动执行代码
        function run(gen) {
            var g = gen()
            function next(data) {
                var result = g.next(data)
                // 结束执行
                if (result.done) return
                result.value.then(function (data) {
                    next(data)
                });
            }
            next();
        }
        run(gen);

@shifengdiy
Copy link

shifengdiy commented Jul 29, 2021

async awit 就是 promise.then的语法糖

const promiseF = function(){
  return  new Promise((resolve) => {
      setTimeout(() =>{
        resolve('done')
      }, 100)
    })
}

const asyncF = async function(){
  console.log(1)
  await promiseF();
  console.log(2)
}

等同于

const asyncF2 = async function(){
  console.log(1) 
  Promise.resolve(promiseF()).then(res => {
    console.log(2)
  })
}

注意
理论上 await 之后必须是一个promise对象,如果await之后是一个普通函数执行或者对象,会被包装成一个promise对象

@lazyhero
Copy link

我想问问,这个题目是要解决的是什么问题?

应该是想问 async/await (Generator)的实现,面试官期望你答出实现、场景(什么时候用await什么时候不用)和坑点,毕竟自古以来JS花活第一名,你可能不这么耍,但可能面试官觉得如果有人耍了花活,你要知道他耍的对不对,有哪些坑;

@Qyokizzzz
Copy link

Qyokizzzz commented Dec 6, 2021

function fn1() {
return new Promise((resolve, reject) => {
fn2()
.then((res) => res.method())
.then((res) => resolve(res.property));
})
}

async function fn1() {
let p1 = await fn2();
let p2 = await p1.method();
return p2.property;
}

两个fn1等价。
async函数的返回值是一个Promise,await可以让代码看起来像同步。

@NAZBIN
Copy link

NAZBIN commented Feb 27, 2022

看到没有好的答案,自己提炼总结一下。

Async/awiat诞生的背景:使用promise能很好的解决回调地狱的问题,但是这种充满了.then()方法,如果处理流程复杂,那么整段代码有很多then,会导致语义化不明确。

Async/await使用了两种技术:Promise和Generator(底层实现机制——协程)

生成器运行过程:生成器Generator是可以暂停执行和恢复执行的。可能还有人对生成器这块不理解,简单说一下:
在生成器函数内部执行一段代码,遇到yield关键字,js引擎会返回关键字后面的内容给外部,并暂停该函数的执行。外部函数可以通过next方法恢复函数的执行。

V8层面抛析暂停与恢复的实现原理:要搞懂函数为什么能暂停和恢复,首先要了解协程的概念。简单来说,可把协程看成是跑在线程上的任务,线程与协程之间是'1 v N'的关系,但是在线程上同一时间只能执行一个协程任务,比如当前执行的是A协程,要启动B的话A就需要将控制权交给B,这就体现在A暂停执行,B恢复执行,我们把A叫做B的父协程。

协程的高效性:协程是完全由程序所控制,也就是在用户态执行,这样的好处就是性能得到了很大的提升,不会像切换线程那样消耗资源。

(上面只是简单描述了协程的执行过程,其实中间还涉及到协程的调用栈切换以及协程的信息返回等等,感兴趣可以再交流)

那在JS中,生成器就是协程的一种实现方式,相信你也理解什么是生成器了。 推荐大家用Generator生成器和Promise来模拟一下async和await的实现。

通过使用生成器配合执行器,就能实现使用同步的方式写出异步代码。基本说到这已经解答了这个问题,中间过程上面的问题 可以再交流。

@Yangfan2016
Copy link

generator 实现的
image

@eric-gitta-moore
Copy link

感觉 generator + Iterator + promise 这么理解似乎没问题,但是具体实现是这么弄得嘛,还是另有道路。google v8、spidermonkey、JavaScript core 都是这么弄的嘛也不太清楚,各个 js engine 的各个版本又是怎么实现的,有没有出入也不太清楚

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests