当前位置:网站首页>异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise
异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise
2022-08-04 02:29:00 【rananie】
文章目录
异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise
Generator生成器函数
面试题
- generator用来做什么, generator函数的使用场景
- 介绍下es6 generator函数
- generator 实现 async await
Generator生成器函数
语法:function * 函数名(){}
是什么
Generator 函数是 ES6 提供的一种异步编程解决方案。内部可以看成一个状态机,返回迭代器对象,调用next方法进入下一个状态。yield表达式时暂停的标志
使用的场景
- Generator 函数会返回一个迭代器对象,因此可以把 Generator 赋值给对象的
Symbol.iterator
属性,从而使得该对象具有 Iterator 接口。 - async/await是自带执行器的Generator对象
Generator生成器函数使用上的补充 了解
generator
函数可以用next
方法来传参,该参数就会被当作上一个yield表达式的返回值。yield表达式本身没有返回值,返回undefined
所以第一次next传参是没用的,只有从第二次开始next传参才有用Generator生成器函数如果有返回值 则是最后一次next的返回值
{value:xxx:done:true}
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
// 有return值
function* gen() {
yield 1
yield 2
yield 3
return 4
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: 4, done: true }
yield
后面跟promise,函数立即执行
function f1(val){
return Promise.resolve(val);
}
function f2(val){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(val)},1000);
})
}
function* gen() {
yield f1(1);
yield f2(2);
return 'ending';
}
const g = gen()
console.log(g.next()) // { value: Promise { 1 }, done: false }
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }
基于Promise对象的简单自动执行器
function f1(val){
return Promise.resolve(val);
}
function f2(val){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(val)},1000);
})
}
function* gen() {
console.log(yield f1(1)); //1
console.log(yield f2(2)); //2
console.log(yield 'xxx');//'xxx'
return 'ending';
}
function run(gen){
var g = gen();
function next(data){
var result = g.next(data); //{ value: Promise { 1 }, done: false }
if (result.done) return result.value; //执行完毕就可以返回
Promise.resolve(result.value).then(function(data){
//获取promise的执行结果
console.log(data); //1,2,xxx
next(data); //将1作为yieId f1()执行的结果
});
}
next();
}
run(gen);
iterator迭代器
iterator迭代器
集合概念有数组、对象、Map、Set,需要有一个统一的接口机制来处理所有不同的数据结构
是什么
迭代器iterator是一种接口,为不同的数据结构提供统一的访问机制
好处
- 为各种数据结构,提供一个统一的、简便的访问接口
- 任何数据结构只要部署 Iterator 接口,就可以完成
for..of
遍历操作 - 使得数据结构的成员能够按某种次序排列
原理是什么?
迭代器对象,有一个next
方法,每次调用next
方法都将返回一个结果。结果值是一个object {value:xxx,done}
,value
表示具体的返回值, done
是布尔类型的,表示集合是否遍历完成。
内部会维护一个指针,用来指向当前集合的位置,每调用一次 next
方法,指针都会向后移动一个位置。
// 如果需要实现逆序:i初始化为items.length-1,依次i--
//[Symbol.iterator] = createIterator
function createIterator(items) {
var i = 0;
return {
//迭代器对象,它具有一个 next 方法,该方法会返回一个对象,包含 value 和 done 两个属性
next: function () {
var done = i >= items.length;
var val = !done ? items[i++] : undefined;
return {
done: done,
value: val
}
}
}
}
//测试
var it = createIterator(['a', 'b', 'c']);
console.log(it.next());// {value: "a", done: false}
console.log(it.next());// {value: "b", done: false}
console.log(it.next());// {value: "c", done: false}
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"
可迭代对象
- 可迭代的数据内部都有
[Symbol.iterator]
的属性,也称为实现了Iterator接口 [Symbol.iterator]
的属性会返回一个函数createIterator函数,创造迭代器对象的方法[Symbol.iterator]
返回的函数执行之后会返回一个迭代器对象[Symbol.iterator]
函数返回的迭代器对象中有一个名称叫做next的方法- next方法每次执行都会返回一个对象
{value: 10, done: false}
- 这个对象中存储了当前取出的数据和是否取完了的标记
使用场景
- for-of 遍历
- 扩展运算符(…)也会调用默认的 Iterator 接口。
- 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
async/await
面试题
- async / await 的特点以及使用场景
- 为什么async/await要成对出现,只有await可以吗?
- async/await内部通过什么来实现的
- 说一下promise和async/await,分别是怎么捕获错误的?如果把promise写在try/catch里面会捕获到错误吗?
- await/async与generator函数的区别
async/await是什么? 使用场景是什么?
是什么
async函数是自带执行器的generator函数,是generator函数的语法糖,当函数执行遇到await
时候, 函数会交出执行权。
有什么作用
async/await是回调地狱的最佳解决方案,以同步的方式去写异步代码。
async函数返回值的状态
async函数返回的Promise对象,必须等到内部所有await
命令后面的 Promise 对象
执行完,才会发生状态改变,除非遇到return语句或者抛出错误。
await/async与generator函数的区别
- 自带执行器的generator函数,不需要通过
next()
到下一个状态 - async函数的返回值是promise,generator返回值是Iterator迭代器对象
- await命令后面,可以是 Promise 对象和原始类型的值,
await
可以看成是then
的语法糖。yield命令后面只能是 Thunk 函数或 Promise 对象,不能是原始类型的值。
await/async内部实现原理 Generator函数和自动执行器
- 用async实现Generator函数
function f1(val){
return Promise.resolve(val);
}
function f2(val){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(val)},1000);
})
}
//async函数写法
async function gen () => {
console.log(await f1(1)); //1
console.log(await f2(2)); //2
console.log(await 'xxx');//'xxx'
return 'ending';
}
//Genrator函数写法
function* gen() {
console.log(yield f1(1)); //1
console.log(yield f2(2)); //2
console.log(yield 'xxx');//'xxx'
return 'ending';
}
- async是自带执行器的Generator 函数
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){
//.....代码
}
//等价于
//将Generator函数 添加自动执行器 变成async函数
function fn(args){
return run(function*(){
//run函数就是自动执行generator函数
//.....代码
})
})
}
//和之前的建议版本一样,增加了检测,next的参数从data变成了函数
function run(genF) {
return new Promise((resolve, reject) => {
const result = genF(); //获取迭代器对象
function getState(nextF) {
let obj;
try {
obj = nextF();//执行迭代器函数 获取value {value:xxx.done:xxx}
} catch(e) {
return reject(e);
}
if(obj.done) {
//迭代器是否执行完毕
return resolve(obj.value);
}
Promise.resolve(obj.value).then(function(val){
getState(function(){
return result.next(val) });
}, function(e){
getState(function(){
return result.throw(e)});
});
}
getState(() => result.next()); //开始执行迭代器
});
}
async错误捕获方式
async错误捕获方式
- await只能得到成功的结果,失败的结果需用try-catch
function f1(val){
return Promise.reject(val*2);
}
async function fn(){
try {
await f1(3);
} catch (error) {
console.log(error); //6
}
}
fn()
- try catch只能捕获同步代码,不能捕获异步代码,在async函数内使用await之后相当于异步代码变成了同步代码。
try catch为什么不可以捕获到异步代码?
try catch是同步代码在执行栈中,异步任务会被推进队列中,根据事件循环机制,会先将执行栈中的代码执行完毕再去执行队列中的任务。
Promise
Promise.all/Promise.race/Promise.allSettled
状态只能改变一次,当状态已经改变时,resolve和reject
进入后立即返回,不会获取data,修改状态执行对应的回调
Promise.all(数组)
:返回一个Promise A- 参数中所有的promise都成功,A的状态为成功,成功的值 为参数promise返回的值 组成的数组,顺序和参数中数组一致。
- 数组中有promise失败,则A的状态为失败,值为第一个失败的值。
Promise.race(数组)
:返回一个promise,结果由第一个完成的promise结果决定。- 使用场景:promise超时请求,控制xxxms后请求超时
Promise.allSettIed(数组)
:返回一个成功的promise,promise的值为数组,数组中包含每个promise执行的状态和返回值,当所有的promise状态都改变后,将数组返回。- 使用场景: 希望等一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。
手写Promise.all/Promise.race/Promise.allSettled
Promise.all()
- 返回一个promise,promise的结果由数组中的元素执行结果决定
- 依次取出数组中元素的执行结果,注意如果元素是值是没有.then的,所以可以Promise.resolve(元素) 来让非promise值也有.then
- 如果成功就按promise在数组中的顺序放进结果数组中,全部成功调用resolve(结果数组)。如果失败就执行reject,表示返回的promise对象失败了
Promise.all = function(iterator){
let count = 0;
let res = new Array(iterator.length);
return new Promise((resolve,reject)=>{
iterator.forEach((element,index) => {
Promise.resolve(element).then(value=>{
count++;
res[index] = value;
if(count == iterator.length) resolve(res);
},reason=>{
reject(resolve)
})
});
})
}
const p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
const p2 = Promise.resolve('success')
const p3 = Promise.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //["成功了", "success"]
}).catch((error) => {
//未被调用
})
Promise.all([p1, p3, p2]).then((result) => {
//未被调用
}).catch((error) => {
console.log(error) //"失败"
});
Promise.allSettled ()
Promise.allSettled = function(iterator){
let res = new Array(iterator.length);
let count = 0 ;
return new Promise((resolve,reject)=>{
const pushResult = (index,status,value)=>{
res[index]= {
status:status,value:value};
count++;
if(count == iterator.length)resolve(res);
}
iterator.forEach((element,index) => {
Promise.resolve(element).then(value=>{
fn(index,'fulfilled',value);
},reason=>{
fn(index,'rejected',reason);
})
});
})
}
手写题:请求五秒未完成则终止
//提供两个模拟API
api = () =>{
};
warning = ()=>{
};
function timing(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject();
},5000)
})
}
function apiTiming(){
const arr = [api(),timing()];
Promise.race(arr).then(res=>{
console.log(res);
}),catch(e=>{
warnning(e);
})
}
promise实现并发的异步任务调度器
实现一个具有并发数量限制的异步任务调度器,可以规定最大同时运行的任务。
JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。完善下面代码的Scheduler类,使以下程序能够正常输出:
class Scheduler {
add(promiseCreator) {
... }
// ...
}
const timeout = time => new Promise(resolve => {
setTimeout(resolve, time);
})
const scheduler = new Scheduler(2);
const addTask = (time,order) => {
scheduler.add(() => timeout(time).then(()=>console.log(order))) //实现1: add方法参数不是一个promise 不用管返回值
scheduler.add(() => timeout(time)).then(()=>console.log(order)) //实现2: add方法参数是一个promise,返回一个promise
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// output: 2 3 1 4
分析
任务调度器-控制任务的执行,当资源不足时将任务加入等待队列,当资源足够时,将等待队列中的任务取出执行
在调度器中一般会有一个等待队列queue
,存放当资源不够时等待执行的任务。
具有并发数据限制,假设通过max
设置允许同时运行的任务,还需要count
表示当前正在执行的任务数量。
当需要执行一个任务A时,先判断count==max
如果相等说明任务A不能执行,应该被阻塞,阻塞的任务放进queue中,等待任务调度器管理。
如果count<max
说明正在执行的任务数没有达到最大容量,那么count++
执行任务A,执行完毕后count--
。
此时如果queue中有值,说明之前有任务因为并发数量限制而被阻塞,现在count<max
,任务调度器会将对头的任务弹出执行。
// output: 2 3 1 4
class Scheduler {
constructor(max) {
this.tasks = [], // 待运行的任务
this.count = 0 // 正在运行的任务
this.max = max;
}
// promiseCreator 是一个异步函数,return Promise
add(promiseCreator) {
//add需要返回promise
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve //将resolve保存起来,等待promiseCreator执行后,在start中调用控制add返回
if (this.count < this.max) {
this.start(promiseCreator)
} else {
this.tasks.push(promiseCreator)
}
})
}
start(promiseCreator) {
this.count++;
promiseCreator().then(() => {
promiseCreator.resolve(); //add返回
this.count--;
if (this.tasks.length) {
this.start(this.tasks.shift())
}
})
}
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time);
})
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
//() => new Promise(setTimeout(resolve, 1000))
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
async和await
class Scheduler {
constructor(max) {
this.tasks= []
this.count = 0;
this.max =max;
}
async add(promiseCreator) {
if(this.count >= this.max) await new Promise(resolve => this.tasks.push(resolve)); //将resolve放入队列中等待执行
this.count++
const res = await promiseCreator()
this.count--
if(this.tasks.length) this.tasks.shift()()
return res
}
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time);
})
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
//() => new Promise(setTimeout(resolve, 1000))
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
同步任务
1.addTask(1000, ‘1’)执行到const res = await promiseCreator()的时候,交出执行权,执行下一步
2.addTask(500, ‘2’)同1
3.addTask(300, ‘3’)此时count = 2 所以执行到await new Promise(resolve => this.tasks.push(resolve))的时候交出执行权,执行下一步
4.addTask(400, ‘4’)同3
异步任务
1.time=500的定时器resolve, addTask(500, ‘2’)函数重获执行权,开始执行其中的后续代码:
this.count-- //count = 1
if(this.tasks.length) this.tasks.shift()() //使waitQueue中的第一个异步任务resolve,addTask(300, '3')重获执行权
return res //遇到return,则async函数返回的Promise的状态resolved, 所以.then(() => console.log(time, order))输出 500 '2'
2.addTask(300, ‘3’)函数重获执行权,执行到const res = await promiseCreator()交出执行权
3.time=300的定时器resolve, 后续流程同5
4.addTask(400, ‘4’)函数重获执行权, 后续同6
5.time=1000的定时器resolve, 后续流程同5
6.time=400的定时器resolve, 后续流程同5
边栏推荐
- 实例039:有序列表插入元素
- esp8266-01s刷固件步骤
- MallBook联合人民交通出版社,推动驾培领域新发展,开启驾培智慧交易新生态
- 2022G1工业锅炉司炉考试练习题及模拟考试
- Hey, I had another fight with HR in the small group!
- STM8S105k4t6c--------------点亮LED
- [Original] Start the XPS/OXPS reader that comes with Windows 10
- 实例037:排序
- flask框架初学-06-对数据库的增删改查
- Multithreading JUC Learning Chapter 1 Steps to Create Multithreading
猜你喜欢
DDTL:远距离的域迁移学习
Parquet encoding
C program compilation and predefined detailed explanation
In the season of going overseas, the localization of Internet tips for going overseas
html select tag assignment database query result
C程序编译和预定义详解
Priority_queue element as a pointer, the overloaded operators
Example 040: Reverse List
持续投入商品研发,叮咚买菜赢在了供应链投入上
Example 037: Sorting
随机推荐
esp8266-01s刷固件步骤
Web APIs BOM- 操作浏览器:swiper 插件
Parquet encoding
esp32 releases robot battery voltage to ros2 (micro-ros+CoCube)
keytool命令
Web APIs BOM - operating browser: swiper plug-in
出海季,互联网出海锦囊之本地化
5. Scrapy middleware & distributed crawler
flask框架初学-06-对数据库的增删改查
STM8S-----选项字节
Example 041: Methods and variables of a class
2022.8.3-----leetcode.899
验证码业务逻辑漏洞
阿里云国际版基于快照与镜像功能迁移云服务器数据
halcon自定义函数基本操作
STM8S项目创建(STVD创建)---使用 COSMIC 创建 C 语言项目
In the season of going overseas, the localization of Internet tips for going overseas
ant-design的Select组件采用自定义后缀图标(suffixIcon属性)时,点击该自定义图标没有反应,不会展示下拉菜单的问题
2022年T电梯修理考题及答案
DDTL:远距离的域迁移学习