异步编程
callback(回调函数)
作用:在早期未出现promise之前,用来处理异步请求
概念:首先是一个函数,其次是作为其他函数的参数,在执行完其他函数之后在执行这个函数,就叫做回调函数
缺点:容易形成回调地狱
js
function sleepCallback(callback, time) {
setTimeout(() => {
callback();
}, time);
}
sleepCallback(() => {
console.log('sleep 1s');
}, 1000);
Promise
概念:简单说就是一个容器,里面保存着某个未来才会结束的事件。
特点:
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 - 实例一旦被创建会立刻执行,如下图
js
function sleepPromise() {
return new Promise((resolve, reject) => {
console.log('sssss');
});
}
sleepPromise() //立即输出
缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消 - 不设置回调函数,
Promise
内部抛出的错误,不会反应到外部 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段
then
js
function sleepPromise() {
return new Promise((resolve, reject) => {
//console.log('sssss');
//异步操作成功时候的结果由resolve函数执行,并带参传递出去
//resolve(xxx)
//异步操作失败时候的结果由reject函数执行,并带参传递出去
//reject(xxx)
//console.log("wwwww")
setTimeout(()=>{
reject("1000ms")
},time)
});
}
1、then方法接受两个可选参数,分别是promise成功的回调和失败的回调,当只有一个回调的时候,则只接受resolve的回调
sleepPromise(1000)
.then((resolve) => {
console.log('resolve success', resolve);
},(reject)=>{
console.log('reject', reject);
})
2、通常更推荐使用catch去接受reject的回调,当then方法只接受了一个回调函数为参数时候,则reject会走catch,当有两个参数时,会执行reject,不会重复执行catch操作
sleepPromise(1000)
.then((resolve) => {
console.log('resolve success', resolve);
},(reject)=>{
console.log('reject', reject); //只执行一次reject
})
.catch((err)=>{
console.log('catch', err);
})
知识点:
1、promise异步执行的结果只能调用resolve或reject函数,并将结果以参数的方式传递出来,由then方法来接受这个参数来做下一步的处理
2、当promise中resolve和reject都存在的时候,只会执行其中的一个,
3、调用`resolve`或`reject`并不会终结 Promise 的参数函数的执行。
4、若then中没有参数也没有使用catch去捕获错误,则promise内部回吃掉错误(如下图)
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
结果如下:
all
概念:Promise.all()
方法接受一个数组作为参数,参数必须都是promise实例,否则会先调用resolve方法,将参数转成promise实例。
情况:
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
(3)如果作为参数的promise实例中,自己定义了catch方法,则并不会触发promise.all的catch方法
代码示例:
js
function sleepPromise(time) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("1000ms",time)
},time)
});
}
const promiseAll = Promise.all([
sleepPromise(1000),
sleepPromise(2000)
]).then(
(resolve) => {
console.log('Promise.all', resolve);
},
(reject) => {
console.log('Promise.all', reject);
}
);
console.log('promiseAll', promiseAll);
备注:promiseAll是一个数组,是有all方法里面的实例的返回值组成的
在all方法中的函数执行是不分先后,等到所有实例都处理完成之后再会去执行then方法或者catch方法。
race
概念:只要有一个实例率先改变,则promiseAll的状态就会跟着改变
js
const promiseAll = Promise.race([
sleepPromise(1000),
sleepPromise(2000)
]).then(
(resolve) => {
console.log('Promise.all', resolve);
},
(reject) => {
console.log('Promise.all', reject);
}
);
console.log('promiseAll', promiseAll);
finally
概念:用于指定不管 Promise 对象最后状态如何,都会执行的操作。
手写promise
当使用promise时,没有使用resolve或reject时,promise的状态永远都是pending状态,后接then方法也是无法执行的,一旦状态从pending变为fulfilled或者rejected,那么此Promise实例的状态就不可以改变了
状态:
- pending:等待中,是初始状态;
- fulfilled:成功状态;
- rejected:失败状态;
实现思路:
js
function sleepPromise(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1000ms', time);
//reject('1000ms', time);
}, time);
});
}
const result = sleepPromise(1000);
console.log('result', result);
result.then((value)=>{
console.log('then',value)
}).catch((err)=>{
console.log("err",err)
})
console.log("then",result)
开始执行: 执行resolve后执行then: 执行reject后执行then:
- 首先promise是一个对象,有两个属性分别是PromiseResult和PromiseState
- 该对象接受一个参数为函数(excutor),自动执行该函数,该函数接受两个参数(resolve和reject),在后续的调用中会选择性的执行这两个函数
- resolve和reject都是promise内部的方法,因此该函数内部this的指向必须是指向promise实例本身
- PromiseState只有一种状态变更,要么执行resolve,从pending变成fulfilled,要么调用reject,从pending变成rejected
- resolve和reject函数都可以传递参数,then方法中第一个接受的参数,则就是resolve传递的参数,而catch接受的则是reject传递的参数
- 当resolve和reject同时调用时,会根据promise的状态来决定执行其中的某一个,一旦执行了其中的某一个则剩下一个不会在执行
- then方法接受接受两个参数,该参数必须是函数并且需要自动执行
简易版
js
class myPromise {
initValue() {
this.PromiseResult = undefined;
this.PromiseState = 'pending';
}
//常见错误:未绑定this
bindThis(){
//将这两个方法绑定到promise实例身上,此时就可以在这个两个方法内部访问到promis实例身上的属性和方法了
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
constructor(excutor) {
//这里的this.resolve和reject只是表示对一个函数的引用,并不会立即调用
this.initValue()
this.bindThis()
excutor(this.resolve, this.reject);
//在constructor中this指向的是实例本身
//console.log("constructor this",this)
}
resolve(value) {
console.log('resolve this',this);
if (this.PromiseState !== 'pending') return;
this.PromiseState = 'fulfilled';
this.PromiseResult = value;
}
reject(reason) {
console.log('reject');
if (this.PromiseState != 'pending') return;
this.PromiseState = 'rejected';
this.PromiseResult = reason
}
}
function sleepPromise(time) {
return new myPromise((resolve, reject) => {
setTimeout(() => {
resolve('1000ms', time);
}, time);
});
}
const result = sleepPromise(10000);
console.log('result', result);
在类和模块的内部,默认是严格模式,所以不需要使用
use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
进阶版(then)
结合上述中then的使用方法我们可以得知:then方法接受两个可选参数,两个参数也都是函数且需要执行内部逻辑,第一个参数接受resolve方法传递来的参数,第二个参数接受reject方法传递的参数
js
then(onfulfilled,onrejected){
console.log("then",this.PromiseState)
//首先保证其次一定是函数
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (val)=> {return val}
onrejected = typeof onrejected === 'function' ? onrejected : (err)=>{ throw err}
console.log("onfulfilled",onfulfilled)
if(this.PromiseState === 'fulfilled'){
onfulfilled(this.PromiseResult)
}else if(this.PromiseState === 'rejected'){
onrejected(this.PromiseResult)
}
}
此时当存在定时器时,此时执行顺序会有问题,resolve方法执行顺序会晚与then方法,then方法执行的时候this.PromiseState的值永远为pending。
优化版本(兼容定时器版本)
优化思路:将then方法中的两个函数先保存在数组中,在resolve或者reject方法执行后在执行数组中对应的方法
js
//在执行resolve的时候判断是否有callback
if (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult);
}
//同理:在执行reject的时候也需要判断
if (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult);
}
//当PromiseState一直为pending状态时,就说明存在异步任务了,需要 then方法中的两个回调函数存储起来了
else if (this.PromiseState === 'pending') {
//解决存在定时器出现的问题
this.onFulfilledCallbacks.push(onfulfilled.bind(this));
this.onRejectedCallbacks.push(onrejected.bind(this));
}
牛逼版本(支持链式调用)
优化思路:then支持链式调用,下一次then执行首上一次then返回值的影响
js
const test3 = new MyPromise((resolve, reject) => {
resolve(100) // 输出 状态:success 值: 200
}).then(res => 2 * res, err => 3 * err) .then(res => console.log('success', res), err => console.log('fail', err))
js
then(onfulfilled, onrejected) {
console.log('then', this.PromiseState);
//首先保证其次一定是函数
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (val) => val
onrejected = typeof onrejected === 'function' ? onrejected : (err) => {
throw err;
};
console.log('onfulfilled', onfulfilled);
//在链式调用的时候必须必须是一个promise对象
const thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = (cb) => {
try {
const x = cb(this.PromiseResult);
if (x === thenPromise) {
throw new Error('error');
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch {
throw new Error('throw err');
}
};
if (this.PromiseState === 'fulfilled') {
resolvePromise(onfulfilled);
} else if (this.PromiseState === 'rejected') {
resolvePromise(onrejected);
} else if (this.PromiseState === 'pending') {
//解决存在定时器出现的问题
this.onFulfilledCallbacks.push(resolvePromise.bind(this,onfulfilled));
this.onRejectedCallbacks.push(resolvePromise.bind(this,onrejected));
}
});
//一定要将promise对象return出去
return thenPromise
}
}
async和await
async
概念:是 Generator 函数的语法糖。
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
js
// Generator用法:
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//async和await的语法:
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
优点:
- Generator 函数的执行必须靠执行器,所以才有了
co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。- 更好的语义。
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。- 更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。- 返回值是 Promise。
async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
实现原理:将 Generator 函数和自动执行器,包装在一个函数里。
js
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
await
概念:正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
个人理解:await相当于return
js
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v)) //123
注意点
:
- 任何一个
await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。 - 如果
await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。 await
命令只能用在async
函数之中,如果用在普通函数,就会报错。- 若await后不跟promise时,是无法达到同步的效果的
js
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
如何实现?
思想
:结合generator函数
generator函数
概念:和普通函数写法上的区别是需要在function后面需要加*,并且只有在generator函数内部才能使用yeild,yeild相当于是函数执行的暂停点,而next就是开始执行点
特点:可以暂停
- value:暂停点后面接的值,也就是yield后面接的值;
- done:是否generator函数已走完,没走完为false,走完为true
js
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 }