一、异步呈现的起因

js是单线程的,一次只能干一件事,所以,所有的事件都像排队一样,等着被执行,然而如果其中一件事件在队列中须要消耗很长的工夫,那下一个事件就会始终在期待。比方咱们关上一个网页,接口申请工夫是5秒,那页面期待5秒之后,再渲染页面,页面就会长工夫留白,影响用户体验。于是,js在同步机制的缺点下设计出了异步模式

举个例子

  • 1.不管你是先切菜还是先用电饭煲煮饭,都要等一个事件结束后能力进行下一项,这就是一个同步的例子
  • 2.切菜的时候你也能够用电饭煲煮饭 (不必等你切完菜)这就是一个异步的例子。上面用代码演示一下异步
function a() {  console.log("a");  setTimeout(() => {    console.log("a1");  }, 1000);}function b() {  console.log("b");}a();b();//打印后果 a,b,a1//这里b不必期待一秒后a1打印完再打印,这里能够联合js的事件循环机制一起看,更加细节// 附上js的事件循环机制文章:https://segmentfault.com/a/1190000039866826

二、异步的解决方案

1.回调函数

回调函数的概念:被作为实参传入另一函数,并在该内部函数内被调用,用以来实现某些工作的函数,称为回调函数

function b(value) {  var bb = "everyone";  console.log(value + bb);}function a(callback) {  let value = "hello ";  setTimeout(() => {    callback(value);  }, 1000);}a(b);//这是一个异步回调,1秒钟之后才会执行b函数

回调函数的毛病:容易写出回调天堂(Callback hell)

  • 嵌套函数存在耦合性,一旦有所改变,就会牵一发而动全身
  • 嵌套函数一多,就很难处理错误(不能用try catch,不能间接return)
let a = setTimeout(function a() {  var name1 = "hello ";  try {    setTimeout(function b() {      var name2 = "everyone " + name1; //如果这里的b函数,name2改变了,上面的c函数打印的值就会受影响,牵一发而动全身      setTimeout(function c() {        var name3 = "yeah!";        return name3; // 在这里return只是为了演示,return name3 不能被接管到        console.log(name2 + name3);      }, 1000);    }, 1000);  } catch (e) {    console.log(e, "不能捕捉谬误");  //这个try,catch只是为了演示,这里不能捕捉到谬误,因为try catch不能捕捉异步谬误,当执行到try时,try外面的代码放到到异步的工作队列里,没有try到任何内容,所以catch外面不打印。让同步代码执行完,去执行异步的工作队列时,这时候会报错。  }}, 1000);console.log(a); //这里a不是name3,所以不能间接return

正是因为回调函数有缺点,所以,es2015诞生了promise

2.Promise

Promise的概念:Promise 对象用于示意一个异步操作的最终实现 (或失败)及其后果值

手写Promise(会围绕下图的办法开展)

Promise的应用

Promise始终是pending的状态,只有当调用resolve或者reject办法之后,状态才会扭转,这时候会依据是胜利还是失败的状态去执行相应的回调函数
new Promise((resolve, reject) => {  setTimeout(() => {    resolve("胜利");    // reject("失败");  }, 1000);}).then(  (value) => {    console.log(value, "这是胜利返回的值");  },  (reason) => {    console.log(reason, "这是失败的起因");  });

以下是手写Promise类外围逻辑的实现

  • Promise就是一个类(或者构造函数,这里只讲类),在执行这个类的时候,须要传递一个执行器(executor)进去,执行器会立刻执行
  • executor外面有两个参数,一个叫resolve(胜利),一个叫reject(失败)
  • 因为resolve和reject可执行,所以都是函数

    class Promise {//类constructor(executor) { // 构造函数  // 胜利  let resolve = () => { };  // 失败  let reject = () => { };  //执行executor可能会报错,把谬误捕捉,传递到reject外面去  try {    executor(resolve, reject);  } catch (err) {    reject(err);  }}}
  • Promise有三种状态,别离为胜利Fulfilled,失败Rejected,期待Pending。

    • Pending--->Fulfilled
    • Pending--->Rejected
    • 一但状态确定就不可更改
  • resolve和reject就是用来更改状态的

    • resolve()--->Fulfilled
    • reject()--->Rejected
const PENDING = "pending"; //期待const FULFILLED = "fulfilled"; // 胜利const REJECTED = "rejected"; // 失败class Promise {  constructor(exector) {    const resolve = (value) => {    // 一但状态确定就不可更改      if (this.status !== PENDING) return;      //更改状态,状态变成胜利      this.status = FUlFILLED;       //把胜利的值放弃下来      this.value = value;    };   const reject = (reason) => {      if (this.status !== PENDING) return;      this.status = REJECTED;      this.reason = reason;    };    try {      exector(resolve, reject); //立刻执行,resolve    } catch (e) {      reject(e);    }  }  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = undefined;  rejectedCallBack = undefined;}
  • then办法外部做的事就是判断状态,如果状态是胜利,就调用胜利的回调函数;状态失败,就调用失败的回调函数。
class Promise{  constructor(executor){...}  status = PENDING;  value = undefined;  reason = undefined;  // then 办法 有两个参数onFulfilled onRejected  then(successCallback, failCallback) {    // 状态为fulfilled,successCallback,传入胜利的值    if (this.status === FULFILLED) {    //用try,catch捕捉在then办法外面抛出的谬误,传入reject中      try {        successCallback(this.value);      } catch (error) {        reject(error);      }    }    // 状态为rejected,执行failCallback,传入失败的起因    if (this.status === REJECTED) {      try {        failCallback(this.reason);      } catch (error) {        this.reject(error);      }    }}
  • 当初根本能够实现简略的同步代码,然而当resolve在setTomeout内执行,then时status还是pending期待状态,所以这时候要把successCallback,failCallback存储起来,一旦resolve或者reject就调用他
  • 当初根本能够实现简略的同步代码,然而同一个promsie屡次调用then,如果resolve或者reject写在setTomeout内执行,只会执行最初一个then你办法,因为failCallback或者failCallback是以一个变量的形式保留的,应该以数组的形式保留

const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; class MyPromise {constructor(exector) {    const resolve = (value) => {    // 一但状态确定就不可更改      if (this.status !== PENDING) return;      //更改状态,状态变成胜利      this.status = FUlFILLED;       //把胜利的值放弃下来      this.value = value;      // if (this.fulfilledCallBack) this.fulfilledCallBack(value);      if (this.fulfilledCallBack) {        this.fulfilledCallBack.map((item) => {          item(value);          return;        });      }    };   const reject = (reason) => {      if (this.status !== PENDING) return;      this.status = REJECTED;      this.reason = reason;      // if (this.rejectedCallBack) this.rejectedCallBack(reason);      if (this.rejectedCallBack) {        this.rejectedCallBack.map((item) => {          item(reason);          return;        });      }    };    try {      exector(resolve, reject);     } catch (e) {      reject(e);    }  }  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = [];  rejectedCallBack = [];  then(successCallback, failCallback) {    if (this.status === FULFILLED) {      successCallback(this.value);    } else if (this.status === REJECTED) {      failCallback(this.reason);     } else {        // this.fulfilledCallBack=successCallback;      // this.rejectedCallBack=failCallback;       // 这里是pending的状态      this.fulfilledCallBack.push(successCallback);        // 用数组把多个胜利或者失败回调存储起来,等到resolve或者reject的时候,顺次执行      this.rejectedCallBack.push(failCallback);    }  }}
  • then办法的参数是可选的

    • then() 相当于----> then(value=>value,(reason)=>{throw reason})

const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; class MyPromise {  constructor(executor){...}  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = [];  rejectedCallBack = [];  then(successCallback, failCallback) {  // 这里是如果then没有传参数,对应的  // then() 相当于----> then(value=>value,(reason)=>{throw reason})  const successCallback = successCallback?successCallback:(value)=>value  const failCallback = failCallback?failCallback:(error)=>{throw error}      if (this.status === FULFILLED) {      successCallback(this.value);    } else if (this.status === REJECTED) {      failCallback(this.reason);     } else {        this.fulfilledCallBack.push(successCallback);        this.rejectedCallBack.push(failCallback);    }  }
  • then办法的链式调用

    • then()返回的也是一个promise对象
    • 把上一个then办法的返回的值传递给下一个then。
    • then() 返回的后果有两种类型,一种是promise对象,这时候在promise的胜利或者失败回调里resolve(value)或者reject(reason)
    • 一种是非promise对象的一般值,间接返回resolve(value)
  • then办法外面不能返回它本人,报“循环援用”谬误

const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; class MyPromise {  constructor(executor){...}  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = [];  rejectedCallBack = [];  then(successCallback, failCallback) {    successCallback = successCallback ? successCallback : (value) => value;    failCallback = failCallback      ? failCallback      : (reason) => {          throw reason;        };    let p = new MyPromise((resolve, reject) => {      //then是链式调用的,1.所以返回promsie对象      // 2.把上一个then返回的值传递上来(resolve(value)或者reject(reason)),如果是promise,判断paromsie的状态,胜利还是失败,调用resolve或者reject把状态告知下一个then办法      if (this.status === FULFILLED) {        setTimeout(() => {        // 这里的定时器是为了失去p, 因为这里p要在new promise()执行完才失去,当初在执行过程中,没法失去,要应用定时器,变成异步,失去 p          try {           // 这里用try catch 是把then()外面的谬误捕捉,传给reject            const x = successCallback(this.value);            getValueType(p, x, resolve, reject);          } catch (e) {            reject(e);          }        });      } else if (this.status === REJECTED) {      // 相似胜利时候的回调        setTimeout(() => {                try {            const x = failCallback(this.reason);            getValueType(p, x, resolve, reject);          } catch (e) {            reject(e);          }        });      } else {        try {          this.fulfilledCallBack.push((value) =>            setTimeout(() =>              getValueType(p, successCallback(value), resolve, reject)            )          );          this.rejectedCallBack.push((reason) =>            setTimeout(() =>              getValueType(p, failCallback(reason), resolve, reject)            )          );        } catch (e) {          reject(e);        }      }    });    return p;  }}// 独自抽出一个办法,判断then返回;// 返回的如果是一般值,间接resolve(value);// 如果返回的是promise对象,先执行then办法,判断状态,在胜利的回调里resolve(value),失败的回调里reject(reason);const getValueType = (p, x, resolve, reject) => {// 判断x 和p 是否相等,相等就是本人调用本人,报“循环援用”谬误  if (p === x) {    return reject(      new TypeError("Chaining cycle detected for promise #<Promise>")    );  }  // 用instanceof判断x是不是MyPromise的实例对象  if (x instanceof MyPromise) {    return x.then(resolve, reject); //简写  } else {    return resolve(x);  }};
  • catch捕捉谬误

    • 返回一个promise谬误
    • 承受一个回调函数,捕捉谬误
    ⚠️留神:这里链式then的失败回调和catch都能捕捉谬误,然而then的失败回调只能捕捉以后promises的谬误,不能捕捉以后then的胜利回调函数外面的谬误。然而catch能捕捉到所有的谬误,所以,链式调用的时候,用catch捕捉谬误
class MyPromise {  constructor(executor){...}  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = [];  rejectedCallBack = [];  then(){...}  catch(failCallBack) {   // 相当于then()第一个参数是undefined    return this.then(undefined, failCallBack);   }}const getValueType =()=>{...}
  • Promise.all(),Promise.race办法

    • Promise.all()容许咱们依照异步代码调用的程序,失去异步代码执行的后果
    • Promise.race()异步代码调用哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态
    class MyPromise {constructor(executor){...}status = PENDING;value = undefined;reason = undefined;fulfilledCallBack = [];rejectedCallBack = [];then(){...}catch() {...}static all(array) {  let result = [];  let key = 0;  return new MyPromise((resolve, reject) => {    function addData(i, value) {         result[i] = value;      key++;      // 判断key === array.length,这时候,所以的异步promise才执行结束      if (key === array.length) {         // 之所以不在for上面执行resolve,在这里执行,因为如果数组外面是异步的promise对象的话,要等它执行完在返回再resolv进来        resolve(result);       }    }    for (let i = 0; i < array.length; i++) {      if (array[i] instanceof MyPromise) {       //是promise对象就把胜利的值push进数组,失败的值reject进来        array[i].then(          (value) => addData(i, value),          (reason) => reject(reason)        );      } else {        addData(i, array[i]); //是一般值就push进数组      }    }    //   resolve(result); // 如果在这里执行resolve的话,不会等promise对象执行结束  });}static race(array) {  return new MyPromise((resolve, reject) => {    for (let i = 0; i < array.length; i++) {      if (array[i] instanceof MyPromise) {      // 胜利返回第一个resolve,失败返回第一个reject        array[i].then(resolve, reject);       } else {      // 会返回第一个resolve,因为状态曾经扭转了,前面resolve不会再执行        resolve(array[i]);       }    }  });}}const getValueType =()=>{...}
  • Promise.resolve(),Promise.reject()办法

    • 如果传入的是promise对象,一成不变的返回
    • 如果传入的是一般值,包装成promise对象返回
    class MyPromise {constructor(executor){...}status = PENDING;value = undefined;reason = undefined;fulfilledCallBack = [];rejectedCallBack = [];then(){...}catch(){...}static resolve(value) {  if (value instanceof MyPromise) return value;  return new MyPromise((resolve, reject) => {    resolve(value);  });}static reject(value) {  if (value instanceof MyPromise) return value;  return new MyPromise((resolve, reject) => {    reject(value);  });}}const getValueType =()=>{...}
  • finally无论失败还是胜利都会执行

    • finally无论后果是胜利或者失败,都会被执行一次
    • finally前面能够链式调用then办法,来拿到以后这个promise最终返回的后果
    • ⚠️留神:这里finally的回调函数可能返回一个promsie对象,那前面的then()要等finally执行结束再执行,这里就要借助resolve办法
class MyPromise {  constructor(executor){...}  status = PENDING;  value = undefined;  reason = undefined;  fulfilledCallBack = [];  rejectedCallBack = [];  then(){...}  catch(){...}  static resolve(value){...}  finally(callBack) {    // 1.首先要晓得状态,那调用then()就能晓得状态    return this.then(      // 2.return进来,是因为要返回一个promsie去链式调用      (value) => {        // callBack();        // return value; // 3.还有把值return进来,便于下一个then()接管        return MyPromise.resolve(callBack()).then(() => value); // 这里是finally的回调函数可能返回一个promsie对象,那就要等这个promsie对象执行结束,再执行前面的then办法,于是,不论是返回一般值还是promsie对象,间接调用resolve()转成promise对象      },      (reason) => {        // callBack();        // throw reason;        return MyPromise.resolve(callBack()).then(() => {          throw reason;        });      }    );  }}const getValueType =()=>{...}

至此,promise的基本功能已大抵实现。其实promise.then也是相似回调函数的思维,只是,辨别了胜利和失败的回调函数,而且then办法的链式调用,把回调函数的嵌套天堂解决了,扁平化了。然而还是没有达到咱们传统的同步代码的可读性,于是Generator呈现了。

3.Generator

  • generator函数2个特点,一个(个别写在function后,function),一个yield。
  • generator函数返回的是一个生成器对象,直到咱们通过.next调用,这个函数的函数体才会执行。
  • 能够利用yield暂停生成器函数这一特点。应用生成器函数,去实现更优的异步体验。
function* fun1() {  console.log("start");  try{      // 在函数外部,能够随时通过yield向外返回一个值      // yield关键词不会向return一样完结这个生成器函数的执行,只是暂停这个生成器函数的执行。直到外界再去执行yield办法时,从yield这个地位持续往下执行      let aa = yield "foo";         // 这里"bar",会作为yield "foo"的返回值,即 aa = "foo"  }catch(error){      console.log(error,"error")  }}const generator = fun1();// 调用fun1函数不会并不会立刻去执行这个函数,而是失去一个生成器对象console.log(generator,"generator") const result = generator.next();// 直到咱们手动调用这个对象的next办法,这个函数的函数体才会开始执行// 在next办法返回的对象{value: "foo", done: false},去拿到yeild返回的值// next办法返回的对象外面的done属性示意这个生成器是否曾经全副执行完了console.log(result,"result")// 调用next传入了参数的话,传入的参数会作为yield语句的返回值 generator.next("bar") // throw办法也是让函数持续向下执行,只不过生成器函数外部抛出一个异样,要用try,catch去捕捉generator.throw("报错啦")

上面看一个例子

//ajax("http://www.baidu.com")函数这里是一个伪代码,假如返回一个promise对象function ajax(){  return new Promise(...)}function* main(){  const data1 = yeild ajax("http://www.baidu1.com")    console.log(data1)  const data2 = yeild ajax("http://www.baidu2.com")    console.log(data2)}const g = main()const result = g.next()  //这里result就是一个生成器对象,value值是yeild ajax("http://www.baidu.com")返回的promise对象result.value.then((value)=>{  const result1 = g.next(value)  // 把promise执行完返回的值,传给data1  if(result1.done) return  result1.value.then((value)=>{      let result2 = g.next(value)  // 把promise执行完返回的值,传给data2      if(result3.done) return       // ...如此往返,能够应用递归的形式  })})

故在Generator函数体中能够拿到result, 这样就能够用同步的写法解决异步问题

封装一个生成器函数执行器(https://github.com/tj/co)

function co(generator){    const g = generator()    function handleResult(result){        if(result.done) return        result.value.then((data)=>{            handleResult(g.next(data))        },(error)=>{            g.throw(error) // 内部用try catch去捕捉        })    }    handleResult(g.next())}// 调用 co(main)

async await

  • async await是generator的语法糖,相比于generator,async await不须要再去配合一个相似于co这样的执行器,外部的执行过程和generator是齐全一样的
  • async函数返回一个promise对象
  • await只能在async函数外面应用

    function* fun() {let a = yield setTimeout(() => console.log("111", 0));let b = yield setTimeout(() => console.log("222", 0));}/** * 等价于 */async function fun() {let a = await setTimeout(() => console.log("111", 0));let b = await setTimeout(() => console.log("222", 0));}

    所以当初咱们大部分状况下会应用async await,它是以简直同步的形式来实现异步