菜鸟进化史之js基础(四)

异步编程

callback(回调函数)

作用:在早期未出现promise之前,用来处理异步请求

概念:首先是一个函数,其次是作为其他函数的参数,在执行完其他函数之后在执行这个函数,就叫做回调函数

缺点:容易形成回调地狱

js 复制代码
      function sleepCallback(callback, time) {
        setTimeout(() => {
          callback();
        }, time);
      }

      sleepCallback(() => {
        console.log('sleep 1s');
      }, 1000);

Promise

概念:简单说就是一个容器,里面保存着某个未来才会结束的事件。

特点:

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
  3. 实例一旦被创建会立刻执行,如下图
js 复制代码
function sleepPromise() {
   return new Promise((resolve, reject) => {
      console.log('sssss');
    });
  }
sleepPromise() //立即输出

缺点:

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消
  2. 不设置回调函数,Promise内部抛出的错误,不会反应到外部
  3. 当处于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)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成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:

  1. 首先promise是一个对象,有两个属性分别是PromiseResult和PromiseState
  2. 该对象接受一个参数为函数(excutor),自动执行该函数,该函数接受两个参数(resolve和reject),在后续的调用中会选择性的执行这两个函数
  3. resolve和reject都是promise内部的方法,因此该函数内部this的指向必须是指向promise实例本身
  4. PromiseState只有一种状态变更,要么执行resolve,从pending变成fulfilled,要么调用reject,从pending变成rejected
  5. resolve和reject函数都可以传递参数,then方法中第一个接受的参数,则就是resolve传递的参数,而catch接受的则是reject传递的参数
  6. 当resolve和reject同时调用时,会根据promise的状态来决定执行其中的某一个,一旦执行了其中的某一个则剩下一个不会在执行
  7. 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());
};

优点:

  1. Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
  2. 更好的语义。asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  4. 返回值是 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

注意点

  1. 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
  2. 如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject
  3. await命令只能用在async函数之中,如果用在普通函数,就会报错。
  4. 若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 }
终极版本(未理解透彻)
相关推荐
寻找沙漠的人14 分钟前
前端知识补充—CSS
前端·css
GISer_Jing26 分钟前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_7482455227 分钟前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v40 分钟前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing43 分钟前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
2401_857600951 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600951 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL1 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
2402_857583491 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
web150850966412 小时前
在uniapp Vue3版本中如何解决webH5网页浏览器跨域的问题
前端·uni-app