文章目录
异步编程解决方案 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对象
原理
将function*
生成器函数进行转换,转换后的代码分成三大块。
context对象
:存储函数执行上下文,Generator实现的核心在于上下文的保存,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样。gen$(_context)
根据yieId语句将代码分割成switch-case块,后续通过切换_context.prev
和_context.next
来分别执行各个case,走到下一步invoke()
方法定义next(),用于执行gen$(_context)
来跳到下一步
// 低配版context
var context = {
next:0,
prev: 0,//
done: false, //是否执行执行完毕
stop: function stop () {
this.done = true
}
}
// 生成器函数根据yield语句将代码分割为switch-case块,后续通过切换_context.prev和_context.next来分别执行各个case
function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}
// 低配版invoke
let gen = function() {
return {
next: function() {
value = context.done ? undefined: gen$(context)
done = context.done
return {
value,
done
}
}
}
}
作者:写代码像蔡徐抻
链接:https://juejin.cn/post/6844904096525189128
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
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.finally的使用和then、catch有什么不同
- promise.catch原理如何实现的
- promise怎么做到链式调用
- 口述promise实现过程
- Promise 的异常处理机制
- promise和await、async的区别
- promise 三种状态 promise的方法有哪些
- promise原理
promise概述
是什么
promise是异步编程解决方案之一,从语法来说Promise是一个构造函数
好处
- 指定回调函数的方式更加灵活
纯回调的方式,执行结束就得不到状态了,必须在执行异步操作之前指定对应的回调函数,先注册回调再执行异步
promise的方式可以将状态暂存,所以成功与失败的回调函数可以之后再指定 - 支持链式调用,可以解决回调地狱问题
状态
- pending:初始化状态
- resolved:成功的状态
- rejected:失败的状态
① pending转化为resolved,调用resolve(value)
② pending转化为rejected,调用rejecte(reason)或者抛出异常
捕获错误的方式
.then()
的第二次参数.catch()
方法
promise知识点 了解
Promise的实现原理
首先需要知道几个问题
状态相关
- 状态改变与指定回调函数的顺序
- 先改变状态,同时指定数据,此时回调函数没有指定,需要先保存状态。
- 先指定回调函数,后改变状态和指定数据,此时不知道调用哪个回调,要将回调函数保存
- promise的状态如何改变
- 执行resolve(value): 如果是pending就会变为resolved
- 执行reject(reason)或抛出异常: 如果是pending就会变为rejected
构造器
- 需要变量保存回调函数和当前promise的状态
- 执行器函数是同步执行的,也就是会立即执行。执行器函数接受两个参数resolve和reject函数。
resolve函数的作用 – reject函数基本差不多
①如果当前promise不是pending不做处理直接返回,如果是resolve函数的作用是将pending改为resolved
②保存resolve函数指定的数据
③如果此时有待执行的回调函数那么依次异步执行成功的回调
then原理-实现链式调用
- 参数有两个,一个成功的回调函数onResolved和失败的回调函数onRejected
- 返回一个新的Promise对象,该Promise的状态由then的执行结果决定
- 返回promise对象,promise的结果由
.then
的回调函数决定
- 如果抛出异常,新promise变为rejected,reason为抛出的异常
- 如果返回的是非promise的任意值,新promise变为resolved,value为返回值。
- 如果返回的是另一个新promise,此promise的结果由新promise的结果决定。
.then
指定回调时,考虑当前的状态,如果此时状态已经改变了说明是先改变状态后指定的回调,回调函数异步调用。如果此时状态还是pending
状态,说明是先指定回调后改变状态,此时需要把回调函数存起来,待状态改变之后再调用- 所以不管是先改变状态还是先指定回调,当最后回调被触发时,都需要获取回调函数的结果,因为需要根据回调函数的结果改变返回的promise的状态。
.then
可以不传失败的回调,那么需要将异常传递下去。如果成功的回调不是函数,将value值传递下去。(catch只会处理失败,所以成功的要继续向后传递成功)
promise.then、catch、finally的原理与实现
类型 | 描述 |
---|---|
promise.then | 可以指定成功和失败的回调 |
promise.catch | 用于指定失败的回调,是特殊的then方法promise.then(undefined,onReject) |
promise.finally | 无论成功和失败都会调用,并且将值和状态原封不动的传递给后面的then |
promise.then的实现原理
- 参数是两个函数,第一个函数为成功时调用的函数,如果没指定将value传递下去。第二个参数为失败时调用的函数,如果没指定,将reason作为错误抛出传递下去
Promise.prototype.then = function (onResolved,onRejected) {
const self =this;
//catch只会处理失败,所以成功的要继续向后传递成功
onResolved =typeof onResolved==='function'?onResolved:value => value;
//实现异常穿透
onRejected =typeof onRejected==='function'?onRejected:reason =>{throw reason};
}
- 返回值为promise对象A,A的状态由
.then
的回调函数返回值
- 如果抛出异常,A的状态为
rejected
,reason为抛出的异常 - 如果返回非promise的值,A的状态为
resolved
,value为返回值 - 如果返回promise对象B,那么A的状态由B执行的结果决定
return new Promise((resolve,reject)=>{
//根据回调函数执行的结果修改返回的promise的状态
function handle(callback) {
try {
const result = callback(self.data);//放在里面,放在外面这个会异步执行,返回获取不到,需要知道结果是什么
if(result instanceof Promise){//情况3
result.then(
value => resolve(value), //如果这个执行说明返回的promise是成功的
reason=> reject(reason)//如果这个执行说明返回的promise是失败的
);//.then才知道promise是成功还是失败
}else {
resolve(result); //情况2
}
} catch (error) {
reject(error);//情况1
}
}
}
.then
指定回调时,考虑当前的状态,如果此时状态已经改变了说明是先改变状态后指定的回调,回调函数异步调用。如果此时状态还是pending
状态,说明是先指定回调后改变状态,此时需要把回调函数存起来,待状态改变之后再调用
//如果先指定回调,需要将回调函数保存起来,回调函数执行完毕后还需要修改新promise对象的状态。这里没设置异步执行的原因是,回调的执行是在构造函数中,在构造函数中已经指定了是异步的了
if(self.status===PENDING){
self.callbacks.push({
onResolved(value){
handle(onResolved);
},onRejected(reason){
handle(onRejected);
}
});
}
//如果先改变状态,后指定回调,状态已经改变了,这里需要指定异步调用
else if(self.status===RESOLVED){
setTimeout(() => {
handle(onResolved);
});
}
else{//如果是reject
setTimeout(() => {
handle(onRejected);
});
}
总结
Promise.prototype.then = function (onResolved,onRejected) {
const self =this;
//catch只会处理失败,所以成功的要继续向后传递成功
onResolved =typeof onResolved==='function'?onResolved:value => value;
//实现异常穿透
onRejected =typeof onRejected==='function'?onRejected:reason =>{throw reason};
//返回一个新的Promise对象
return new Promise((resolve,reject)=>{
//根据回调函数执行的结果修改返回的promise的状态
function handle(callback) {
try {
const result = callback(self.data);//放在里面,放在外面这个会异步执行,返回获取不到,需要知道结果是什么
if(result instanceof Promise){//情况3
// result.then(
// value => resolve(value), //如果这个执行说明返回的promise是成功的
// reason=> reject(reason)//如果这个执行说明返回的promise是失败的
// );//.then才知道promise是成功还是失败
result.then(resolve,reject);//简洁写法
}else {
resolve(result); //情况2
}
} catch (error) {
reject(error);//情况1
}
}
//如果先指定回调,需要将回调函数保存起来,回调函数执行完毕后还需要修改新promise对象的状态。这里没设置异步执行的原因是,回调的执行是在构造函数中,在构造函数中已经指定了是异步的了
if(self.status===PENDING){
self.callbacks.push({
onResolved(value){
handle(onResolved);
},onRejected(reason){
handle(onRejected);
}
});
}
//如果先改变状态,后指定回调,状态已经改变了,这里需要指定异步调用
else if(self.status===RESOLVED){
setTimeout(() => {
handle(onResolved);
});
}
else{//如果是reject
setTimeout(() => {
handle(onRejected);
});
}
}
}
promise.finally(callback)
参数是callback函数,返回值是promise对象A,A的状态由finally前面的promise决定
无论成功和失败都会调用,并且将值和状态原封不动的传递给后面的then
1.需要获取前面promise执行的值,通过.then
2.需要执行callback函数,callback函数可能是一个异步函数,需要等待它执行完毕。通过Promise.resolve
包装,就可以通过.then
知道callback什么时候执行结束。
Promise.prototype.finally = function(callback){
let P = this.constructor
return this.then(
val => P.resolve(callback()).then(()=>val),//这返回的val是前面promise的结果,不是后面callback的结果
reason => P.resolve(callback()).then(()=>{throw reason})
)
}
//测试代码
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => console.log("Promise ready"))
.then(result => console.log(result))
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(promises){
let count = 0;
let res = new Array(promises.length);
return new Promise((resolve,reject)=>{
promises.forEach((promise,index) => {
Promise.resolve(promise).then(value=>{
count++;
res[index] = value;
if(count == promises.length) resolve(res);
},reason=>{
reject(reason)
})
});
})
}
const p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
const p3 =new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
}, 1000);
})
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([p1, p2,p3]).then((result) => {
console.log(result)
/*
[
{ status: 'fulfilled', value: '成功了' },
{ status: 'fulfilled', value: 'success' },
{ status: 'rejected', reason: '失败' }
]
*/
}).catch((error) => {
console.log(error)
})
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);
})
}