Promise、async/await的理解以及区别
promise、async、await有什么用呢,怎么理解?都是为了解决异步回调产生的。Promise好比容器,里面存放着一些未来才会执行完毕的事件的结果,而这些结果一旦生成是无法改变的。async和await遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。promise与async、await的区别?prom
promise、async、await有什么用呢,怎么理解?
都是为了解决异步回调产生的。
Promise好比容器,里面存放着一些未来才会执行完毕的事件的结果,而这些结果一旦生成是无法改变的。
async和await遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
Promise解决了什么问题呢?底层代码是怎样的?
在之前没有promise的时候,我们处理多个异步请求回调是一层一层嵌套的,第一个函数的输出是第二个函数的输入,比如:
ajax.get(url, function(id) {
ajax.get({id}, function() {
ajax.get({name}, function() {
......
})
})
})
如果业务逻辑复杂,且基本上我们对于请求回来的数据还得做一系列的处理,这样的代码对于后期的可阅读性和可维护性都十分不友好,那么promise的链式调用就解决了多层异步嵌套回调的问题。且代码可读性和可维护性都会提高。
接下来我们来实现一下Promise的简易版
先来简述一下思路:
- promise有三种状态pending、fulfilled、reject
- new Promise调用的时候需要传入一个executor执行器函数,该函数会立即执行;
- executor函数接收两个参数,resolve,reject,分别对应异步请求成功执行和失败执行;
- 设置默认状态stratus为pending,请求成功状态下的值为value,默认值为undefined,请求失败下的值为reason,默认值为unfined
- promise的状态值只能从pending -> fulfilled 或者 pending -> reject,状态一旦确定就不会在改变
- promise有一个then方法,接收两个参数onFulfilled、onRejected,分别为异步请求成功的回调和失败的回调。
好了,接下来来实现一个简易版的Promise
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';
class MyPromise {
constructor(executor) {
this.status = PENDING; // 设置默认状态值
this.value = undefined; // 异步请求成功的值,默认为undefined
this.reason = undefined; // 异步请求失败的值,默认为undefined
// 异步请求成功调用
let resolve = value => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
// 异步请求失败调用
let reject = reason => {
if (this.status === PENDING) {
this.status = REJECT;
this.reason = reason;
}
}
try {
// 立即执行
executor(resolve, reject);
} catch(error) {
// 抛异常执行失败逻辑
reject(error);
}
}
// then方法,根据状态值判断是执行成功回调还是失败回调
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECT) {
onFulfilled(this.reason);
}
}
}
可以测试一次,正常输出,哇哦,实现了,但是不要着急,它只是实现了同步操作的Promise,那么我们来继续优化我们的代码
new MyPromise((resolve, reject) => {
resolve('hello world!');
}).then((res) => {
console.log(res); // hello world!
})
如果在executor中传入一个异步操作呢,会有什么样的结果呢?
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('hello world');
}, 1000)
}).then((data) => {console.log(data)})
我们发现什么输出也没有,这个是为什么呢?
因为MyPromise调用then方法,当前的promise并没有成功,一直处于pending状态,所以我们需要当调用then方法时,如果当前状态是pending,需要将成功和失败的回调分别存放起来,在executor的异步任务被执行时,触发resolve或reject,再依次调用成功和失败的回调。根据这个思路来优化一下代码
// 三个状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECT = 'REJECT'
// 基础版的Promise
class MyPromise {
constructor(executor) {
this.status = PENDING; // 设置默认状态
this.value = undefined; // 存放成功状态的值,默认为undefined
this.reason = undefined; // 存放失败状态的值,默认为undefined
this.onResolveCallbacks = []; // 存放成功的回调
this.onRejectedCallbacks = []; // 存放失败的回调
// 成功回调
let resolve = value => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn()); // 依次执行成功回调
}
}
// 失败回调
let reject = reason => {
if (this.status === PENDING) {
this.status = REJECT;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn()); // 依次执行失败回调
}
}
try {
// 立即执行,将resolve和reject函数传给使用者
executor(resolve, reject);
} catch(e) {
// 发生异常时执行失败逻辑
reject(e);
}
}
// 包含一个then方法,并接收两个参数
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECT) {
onRejected(this.reason);
}
// 如果状态为pending,需要依次将onFulfilled和onRejected函数保存起来,等待状态确定后再依次的将对应的函数执行
if (this.status === PENDING) {
this.onResolveCallbacks.push(() => {
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}
再次给executor传一个异步函数执行一下,发现过了一秒后输出了,哈哈哈,解决了异步操作的Promise
通过以上代码的编写,发现了这个其实是一个发布订阅模式:收集依赖 -> 触发通知 -> 取出依赖执行,这种方式被广泛用于发布订阅模式的实现。
promise的优势在于可以链式调用,在我们使用Promise的时候,当then函数中return了一个值,不管是什么值,我们都能在下一个then中获取到,这个就是所谓的then的链式调用,而且当我们不在then中放入参数,比如:promise.then().then(),那么其后面的then依旧可以得到之前then返回的值,这个就是所谓的值的穿透。那么具体应该怎么实现呢?
如果每次调用then的时候都重新创建一个promise对象,并把上一个then的返回结果传给这个新的promise的then方法,不就可以一直then下去了嘛,那么我们来捋一下思路:
- then的参数有onFulfilled onRejected,可以缺省,如果这两个参数不为函数将其忽略,且依旧可以在下面的then中获取到之前返回的值
- promise可以then多次,每次执行完promise.then返回的都是一个新的promise
- 如果then的返回值 x 是一个普通值,那么就将这个结果作为参数传递给下一个then的成功的回调
- 如果then抛出了异常,就把这个异常作为参数传给下一个then的失败的回调
- 如果then的返回值 x 是一个promise对象,那么等待这个promise执行完毕,执行成功则走下一个then的成功回调,失败就走下一个then的失败回调;抛异常走下一个then的失败
- 如果then的返回值 x 和promise是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个then的失败回调
- 如果then的返回值 x 是一个promise,且该返回值同时调用resolve和reject,则第一次调用优先,其他调用忽略。
完整代码如下:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成,是一个错误的实现,用一个类型错误结束掉promise
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
let called;
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
reject(r);
})
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(r);
}
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolveCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolveCallbacks.forEach(fn => fn());
}
}
let reject = reason => {
if (this.status === PENDING) {
this.status = REJECT;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 解决 onFufilled,onRejected没有传值的问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
// 因为错误的值要让后面访问到,所以这里也要抛出个错误,不然会在之后then的resolve中捕获
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === PENDING) {
this.onResolveCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
});
this.onRejectedCallbacks.push(()=> {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
});
}
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject;
}
}, 0)
}
if (this.status === REJECT) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0)
}
})
return promise2;
}
}
promise与async、await的区别?
- promise的出现解决了传统callback函数导致的地狱回调问题,但是他的语法导致它纵向发展形成了一个回调链,遇到复杂的业务场景显然是不美观的;
- async、await看起来更加简洁,使得异步代码看起来像同步代码,只有await的代码执行完毕后才会执行下面的代码,与promise一样,也是非阻塞的;
- async/await基于Promise实现,相当于Promise的升级版,不能用于普通的回调函数;
总体来说它们是解决的同一个问题,达到的效果也是大同小异的,可以根据自己的需求自行选择使用哪个。
async和await的使用
要理解async函数,首先得来了解一下 Generator 函数。因为 async和await遵循的是 Generator 函数的语法糖
Generator函数生成器的理解:
是es6引入的一个数据类型,相当于一个状态机,内部封装了很多状态,同时返回一个迭代器对象。可以通过这个迭代器遍历相关的值和状态。
Generator的显著特点是可以多次返回,每次的返回值作为迭代器的一部分保存下来,可以被我们显式调用。
Generator函数的调用:
- 普通的函数通过 function 声明,return作为回调,可以回调一次;
- Generator通过 function* 定义,除了 return 还使用 yield 返回多次。
Generator的方法:next、return、throw
- next 是一步一步的来执行,每次返回一个值,每次也可以传入新的值参与计算;
- return 则直接跳过所有步骤,返回{value: undefined, done: true};
- throw 则根据函数中书写try/catch方法,返回catch中的内容,如果没有try,则直接抛错。
Generator返回值:
- 是一个带有状态的Generator实例。可以仅能通过for of调用遍历,返回他的所有状态。
- 调用 for of 方法后,在后台调用next(), 当 done 属性为 true 的时候,循环退出。因此Generator函数的实例将顺序执行一遍,再次调用时,状态为已完成。
状态的存储和改变:
yield返回的值是可以被变量存储和改变的。next中传入的参数是会替换掉上一步生成值的。
下面通过一个简单例子看一下Generator函数的使用:
function foo*(n) {
let a = yield n + 1;
let b = yield a + 2;
yield n;
yield a;
yield b;
}
let result9 = foo(0);
result.next(); // {value: 1, done: false}
result.next(5); // {value: 7, done: false} 传入参数5,会替换掉上一步计算的结果,即 a = 5
result.next(6); // {value: 0, done: false} 传入参数6,会替换掉上一步计算的结果,即 b = 6
result.next(7); // {value: 5, done: false}
result.next(8); // {value: 6, done: false}
yield*委托:在Generator函数中,我们有时需要将多个迭代器的值合在一起,我们可以使用yield *的形式,将执行委托给另外一个Generator函数
function* foo1() {
yield 1;
yield 2;
return "foo1 end";
}
function* foo() {
yield* foo1();
yield 5;
}
const result = foo();
result.next(); // {value: 1, done: false}1
result.next(); // {value: 2, done: false}
result.next(); // {value: 5, done: false}
result.next(); // {value: undefined, done: true}
发现foo1 end未输出。并且return的作用不就是执行到就会直接跳出整个流程,为什么还会向后执行呢?
原因是在委托的时候,所有的yield*都是以函数表达式的形式出现。return的值是表达式的结果,在委托结束之前其内部都是暂停的,等待到表达式的结果的时候,将结果直接返回给foo。此时foo内部没有接收的变量,所以未打印。
以上总体例子如下:
function* foo(n) {
yield n+1;
try {
yield n+2;
return n+3;
yield n+6;
} catch(e) {
console.log('catch it');
}
}
const result = foo(1);
// 情况一
result.next(); // {value: 2, done: false}
result.throw(); // 抛错,因为还没进到try方法里面
result.next(); // {value: undifined, done: true}
// 情况二
result.next(); // {value: 2, done: true}
result.next(); // {value: 3, done: true}
result.throw(); // catch it {value: undefined, done: true}
result.next(); // {value: undifined, done: true}
// 遍历
let arr = [];
for (let i of result) {
arr.push(i);
}
console.log(arr); // [2, 3] 因为return了,后面的就不会执行了。
简单的实现一个async/await
// 定义Promise,用来模拟异步请求
function getNum(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num + 1);
}, 1000)
})
}
// 自动执行器,如果一个Generator函数没有执行完则递归调用。
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
promise的简单使用
// 获取用户信息
const getUserInfo = new Promise((resolve, reject) => {
axios.get(url).then(({data}) => {
resolve(data);
}).catch(({err}) => {
reject(err);
})
})
// getUserInfo返回一个promise,可以用它的then方法注册成功回调。这种方式使的异步调用变的十分顺手。
getUserInfo.then(data => {
// 一些处理
console.log(data);
})
更多推荐
所有评论(0)