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
Comments
no body? |
where is dalao? |
Async/Await 其实是generate函数的语法糖,想搞清楚用同步的方式实现异步只要搞清generate函数内部的机制就好了,不知道对不对- - |
Async/Await本来就是异步,那有同步可言 |
@shizhihua666 以同步的方式实现异步,不是说他是同步 |
这个应该是考察基本使用 function getFoo(){
return new Promise(resolve => setTimeOut( () => resolve('foo') , 1000))
}
async function asyncFn(){
let foo = await getFoo()
let logic = '同步是加引号的'
} |
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);
}); |
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)
}) |
@shizhihua666 厉害啊 兄弟 |
应该是下面这张写法吧(链式):
|
U waiyu good |
可以参考你不知道的js一书的中册里面的生成器与迭代器章节看看 |
题目是不是应该改成“如何通过异步的方式实现同步”? |
嗯,也可以
发自我的iPhone
…------------------ 原始邮件 ------------------
发件人: welk <notifications@github.com>
发送时间: 2019年7月13日 17:31
收件人: Advanced-Frontend/Daily-Interview-Question <Daily-Interview-Question@noreply.github.com>
抄送: cooldrangon <2240273677@qq.com>, Comment <comment@noreply.github.com>
主题: 回复:[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.
|
我猜这道题的意思是,async,await 如何通过 同步方式的写法,达到的异步的效果,如 async function a() {
const b = 1;
fooFn();
const res = await barFn(b); // 和同步的 fn 调用没有区别,却可以完成异步的效果
bazFn(res);
...
} 如果是这样的话,那其实就是问 async, await 的实现机制,换句话说就是问 generator 的实现机制,提示一个词 协程 以上一点小见解,可能是错的~ |
是想考查async/await的实现原理吧?我只知道async/await是使用generator+run函数(自动执行generator),但是详细实现原理目前我还未理解,暂时也还没有看到有人对async/await 的实现原理有通俗易懂的讲解,还有很多细节没搞懂,不知道有没有正确理解题意? |
这个题的意思是不是 async/await 如何通过es5 进行实现? 表述的时候可能有问题吧. |
Async/Await 如何通过同步的方式实现异步首先想要更好的理解 Async/Await,需要了解这两个知识点:
背景首先,js 是单线程的(重复三遍),所谓单线程, // 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 操作,定时器),由于需要等服务器响应,就先不理会,而是去做其他的事儿,等请求返回了结果的时候再说(即异步)。 // 网络请求
$.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 操作有了结果(无论成功与否)才会执行第三个参数: 从上面我们就可以看出,实现异步的核心就是回调钩子,将 cb 作为参数传递给异步执行函数,当有了结果后在触发 cb。想了解更多,去看看 至于 async/await 是如何出现的呢,在 es6 之前,大多 js 数项目中会有类似这样的代码: ajax1(url, () => {
// do something 1
ajax2(url, () => {
// do something 2
ajax3(url, () => {
// do something 3
// ...
});
});
}); 这种函数嵌套,大量的回调函数,使代码阅读起来晦涩难懂,不直观,形象的称之为回调地狱(callback hell),所以为了在写法上能更通俗一点,es6+陆续出现了 ========================= 我是分割线 ========================== 以上只是铺垫,下面在进入正题 👇,开始说道说道主角: ========================= 我是分割线 ==========================
所以了解 而 所以就得先讲一讲 而 终于找到源头了:协程 协程
协程是一种比线程更加轻量级的存在,是语言层级的构造,可看作一种形式的控制流,在内存间执行,无像线程间切换的开销。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。 协程概念的提出比较早,单核CPU场景中发展出来的概念,通过提供挂起和恢复接口,实现在单个CPU上交叉处理多个任务的并发功能。 那么本质上就是在一个线程的基础上,增加了不同任务栈的切换,通过不同任务栈的挂起和恢复,线程中进行交替运行的代码片段,实现并发的功能。 其实从这里可以看出 「协程间的调用是逻辑上可控的,时序上确定的」 那么如何理解 js 中的协程呢?
协程实现这里是一个简单的例子证明协程的实用性。假设这样一种生产者-消费者的关系,一个协程生产产品并将它们加入队列,另一个协程从队列中取出产品并消费它们。伪码表示如下: var q := 新建队列
coroutine 生产者
loop
while q 不满载
建立某些新产品
向 q 增加这些产品
yield 给消费者
coroutine 消费者
loop
while q 不空载
从 q 移除某些产品
使用这些产品
yield 给生产者 v8 实现源码:js-generator、runtime-generator 编译模拟实现(es5):regenerator 通过以上,我假装你明白什么是协程,下一步开始说一说迭代器 Iterator
一个对象要变成可迭代的,必须实现
当一个对象需要被迭代的时候(比如开始用于一个 迭代器协议:产生一个有限或无限序列的值,并且当所有的值都已经被迭代后,就会有一个默认的返回值 当一个对象只有满足下述条件才会被认为是一个迭代器: 它实现了一个
根据上面的规则,咱们来自定义一个简单的迭代器: 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 等方法如何遍历一个数组?参考答案
了解了迭代器,下面可以进一步了解生成器了 Generator
生成器函数(GeneratorFunction):
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器对象,当这个迭代器的 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} 看到这里,你可能会问, 我们来总结一下 下面来看看,用 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 } 以上代码是 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 }
}); 说了这么多,怎么还没有到 💡 分析下面 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 ); 参考答案
Async/Await首先, // 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(); 可以看到, 现在再来看看
res.then(data => {
console.log(data); // done
}); 最后咱们来总结一下: 优点:
注意点:
// 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 数组,如何实现接口的继发和并发?参考答案
啊,终于完了,一个 【参考】:
===🧐🧐 文中不足,欢迎指正 🤪🤪=== |
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); |
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)}) |
|
Async/Await 是函数Generator的语法糖. |
@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(); |
首先,这是个细节,你看的很仔细,问题也问得不错, 你的猜想不正确,绑定与否,都可以正确访问到 区别就是,在构造器里面绑定后(用箭头函数也是可以的,看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(); |
@Mr-jiangzhiguo 原来是这样,明白了,谢谢解惑😄 |
|
赞同,那一部分我看了3遍,真的很精彩。就是generator和Promise的语法糖,利用generator可暂停机制和消息传递机制实现的。太nb了。 |
其实本质上还是异步的,只是把每次异步后续的执行放到了对应的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");
});
};
} |
// 删除指定索引节点
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;
} 请教一下,这里为什么不直接获取 // 删除指定索引节点
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;
} |
除了首尾,每个节点都是前后关联的,它来自前一个节点,指向后一个节点, 其实删除本质就是将它前一个节点的指针,指向它下一个节点。也就是说把当前节点从这个链中剥离 |
不是吧,本质还是异步,只不过写法上看起来是同步,所以是 用同步写法(async/await)实现了异步 |
我觉得把题目改成 怎么用同步的方式实现异步 会更好! |
这问题看的有点莫名其妙,可能需要改成 async/await是如何实现的会好一些 |
v8 来讲的话,难道不是通过协程实现的么,外加 promise |
�v8底层协程的概念,来控制异步和同步的执行权的管理。而为了兼容浏览器差异,才有了类似regenerator这样的库通过swich case 的方式来模拟协程。 |
async/await 就是一个自执行的Generator, 另外返回值一定是Promise. |
|
老实说,await如何通过同步的方式实现异步不是因为await之后的所有语句都会被放到微任务队列吗?个人觉得解释async/await的时间原理什么的肯定是更好,但是他为什么“同步”了,就是因为他在执行上把await之后的语句都“异步”了,保证了await之后的语句都在await的回调之后执行吧。(小白乱说的) |
async基础技术使用了生成器和promise,生成器是协程的实现,利用生成器实现生成器函数的暂停和恢复。 |
我想问问,这个题目是要解决的是什么问题? |
this._head.element = this._head.next; 是不是应该改为 this._head = this._head.next; 才对呢 |
async/await 组合可以像写同步代码那样写异步代码。比如有 3 个 ajax 请求,每个请求都依赖上一个的结果,可以写成:
|
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); |
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)
})
} 注意 |
应该是想问 async/await (Generator)的实现,面试官期望你答出实现、场景(什么时候用await什么时候不用)和坑点,毕竟自古以来JS花活第一名,你可能不这么耍,但可能面试官觉得如果有人耍了花活,你要知道他耍的对不对,有哪些坑; |
function fn1() { async function fn1() { 两个fn1等价。 |
看到没有好的答案,自己提炼总结一下。 Async/awiat诞生的背景:使用promise能很好的解决回调地狱的问题,但是这种充满了.then()方法,如果处理流程复杂,那么整段代码有很多then,会导致语义化不明确。 Async/await使用了两种技术:Promise和Generator(底层实现机制——协程) 生成器运行过程:生成器Generator是可以暂停执行和恢复执行的。可能还有人对生成器这块不理解,简单说一下: V8层面抛析暂停与恢复的实现原理:要搞懂函数为什么能暂停和恢复,首先要了解协程的概念。简单来说,可把协程看成是跑在线程上的任务,线程与协程之间是'1 v N'的关系,但是在线程上同一时间只能执行一个协程任务,比如当前执行的是A协程,要启动B的话A就需要将控制权交给B,这就体现在A暂停执行,B恢复执行,我们把A叫做B的父协程。 协程的高效性:协程是完全由程序所控制,也就是在用户态执行,这样的好处就是性能得到了很大的提升,不会像切换线程那样消耗资源。 (上面只是简单描述了协程的执行过程,其实中间还涉及到协程的调用栈切换以及协程的信息返回等等,感兴趣可以再交流) 那在JS中,生成器就是协程的一种实现方式,相信你也理解什么是生成器了。 推荐大家用Generator生成器和Promise来模拟一下async和await的实现。 通过使用生成器配合执行器,就能实现使用同步的方式写出异步代码。基本说到这已经解答了这个问题,中间过程上面的问题 可以再交流。 |
感觉 generator + Iterator + promise 这么理解似乎没问题,但是具体实现是这么弄得嘛,还是另有道路。google v8、spidermonkey、JavaScript core 都是这么弄的嘛也不太清楚,各个 js engine 的各个版本又是怎么实现的,有没有出入也不太清楚 |
No description provided.
The text was updated successfully, but these errors were encountered: