Daily-Interview-Question icon indicating copy to clipboard operation
Daily-Interview-Question copied to clipboard

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

Open yygmind opened this issue 5 years ago • 51 comments

yygmind avatar Jul 08 '19 02:07 yygmind

no body?

chenzesam avatar Jul 09 '19 05:07 chenzesam

where is dalao?

wdzyy avatar Jul 09 '19 05:07 wdzyy

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

xzzdll avatar Jul 09 '19 05:07 xzzdll

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

shizhihua666 avatar Jul 09 '19 06:07 shizhihua666

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

xzzdll avatar Jul 09 '19 07:07 xzzdll

这个应该是考察基本使用

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

hiblacker avatar Jul 09 '19 09:07 hiblacker

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

具体见这里

NathanHan1 avatar Jul 09 '19 09:07 NathanHan1

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)
})

YOMXXX avatar Jul 10 '19 02:07 YOMXXX

@shizhihua666 厉害啊 兄弟

nenezsn avatar Jul 10 '19 08:07 nenezsn

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

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

arcsin1 avatar Jul 10 '19 09:07 arcsin1

where is dalao?

U waiyu good

liuliudaren avatar Jul 11 '19 08:07 liuliudaren

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

cooldrangon avatar Jul 12 '19 05:07 cooldrangon

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

iamwelk avatar Jul 13 '19 09:07 iamwelk

嗯,也可以

发自我的iPhone

------------------ 原始邮件 ------------------ 发件人: welk [email protected] 发送时间: 2019年7月13日 17:31 收件人: Advanced-Frontend/Daily-Interview-Question [email protected] 抄送: cooldrangon [email protected], Comment [email protected] 主题: 回复:[Advanced-Frontend/Daily-Interview-Question] 第 9 题:Async/Await 如何通过同步的方式实现异步 (#156)

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

cooldrangon avatar Jul 13 '19 09:07 cooldrangon

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

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

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

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

liam61 avatar Jul 14 '19 08:07 liam61

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

yang131323 avatar Jul 15 '19 12:07 yang131323

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

qszy1210 avatar Jul 16 '19 09:07 qszy1210

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

daolou avatar Jul 17 '19 02:07 daolou

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

hohenheimsd avatar Jul 17 '19 07:07 hohenheimsd

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)})

zzNire avatar Aug 01 '19 13:08 zzNire

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

yaodongyi avatar Aug 05 '19 07:08 yaodongyi

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

Buzz888 avatar Sep 10 '19 13:09 Buzz888

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

yft avatar Sep 26 '19 12:09 yft

@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

daolou avatar Sep 26 '19 13:09 daolou

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

yft avatar Sep 26 '19 15:09 yft

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
结束了

MyObjects avatar Nov 12 '19 03:11 MyObjects

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

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

asmallgod avatar Dec 12 '19 03:12 asmallgod

image 字打错了...

Hideer avatar Dec 24 '19 05:12 Hideer

image 字打错了...

😅,已更正

daolou avatar Dec 24 '19 08:12 daolou

其实本质上还是异步的,只是把每次异步后续的执行放到了对应的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");
    });
  };
}

ZangYuSong avatar Jan 03 '20 06:01 ZangYuSong