关于javascript:JS异步

36次阅读

共计 12166 个字符,预计需要花费 31 分钟才能阅读完成。

一、异步呈现的起因

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,它是以简直同步的形式来实现异步

正文完
 0