Promise理论及手写

预备知识

错误的类型

Error:所有错误的父类型

ReferenceError:引用的变量不存在

TypeError:数据类型不正确

let b
console.log(b.xxx)
// TypeError:Cannot read property 'xxx' of undefined

let c = {}
c.xxx()
// TypeError:c.xxx is not a function

RangeError:数据值不在其所允许的范围内

SyntaxError:语法错误

错误处理

抛出错误:throw error

捕获错误:try … catch

错误对象:

massage属性:错误相关信息

stack属性:函数调用栈记录信息

try {
  let d
  console.log(d.xxx)
} catch (error) {
  console.log(error.message)
  console.log(error.stack)
}
console.log('出错之后')
// Cannot read property 'xxx' of undefined
// TypeError:Cannot read property 'xxx' of undefined
// 出错之后

这里因为错误被捕获处理了,后面的代码才能够运行下去,打印出 出错之后

promise的理解和使用

promise是ES6规范中的一门新的技术,是js中进行异步编程的新的解决方案

promise支持链式调用,可以解决回调地狱,并且它指定回调的方式更加地灵活。

异步编程包括:

1、fs文件操作

// 采用了node中的fs模块 所以不能在浏览器中运行,只能写js脚本放到node中运行
// 使用promise进行读取
const p = new Promise((resolve,reject) => {
  const fs = require('fs');
  fs.readFile("./content.tx",(err,data) => {
    if(err) reject(err);
    else resolve(data.toString());
  });
})
p.then(value => {
  console.log(value);
},reason => {
  console.log('now in promise')
  console.log(reason);
})

2、数据库操作

3、Ajax

// 使用了ajax,node中没有 所以可以在浏览器中运行 直接写在html文件中
<script>
    let btn = document.querySelector('button');
    btn.addEventListener('click', function() &#123;
      const p = new Promise((resolve,reject) => &#123;
        // 开始请求过程
        // 1、创建对象
        const xhr = new XMLHttpRequest();
        // 2、初始化
        xhr.open('GET','https://zyydgrbk.top');
        // 3、发送
        xhr.send();
        // 4、接收
        xhr.onreadystatechange = function() &#123;
          if(xhr.readyState === 4) &#123;
            if(xhr.status >= 200 && xhr.status < 300) &#123;
              resolve(xhr.response);
            &#125; else &#123;
              reject(xhr.status);
            &#125;
          &#125;
        &#125;
      &#125;);

      p.then(value => &#123;
        console.log(value);
      &#125;, reason => &#123;
        console.log('now in promise')
        console.log(reason);
      &#125;)
    &#125;)
  </script>

4、定时器

// 写了一个中奖的定时器 写在promise中
  <!-- 改写成promise -->
  <script>
    let btn = document.addEventListener('click', IsZhong);
    function IsZhong() &#123;
      let n = Math.round(Math.random() * 100);
      const p = new Promise((resolve,reject) => &#123;
        setTimeout(() => &#123;
          if(n<=30) &#123;
            resolve(n);
          &#125; else &#123;
            reject(n);
          &#125;
        &#125;, 1000);
      &#125;);
      p.then(value => &#123;
        console.log(value);
        console.log('恭喜你,中奖了!');
      &#125;, reason => &#123;
        console.log(reason);
        console.log('很遗憾,未中奖!');
      &#125;)
    &#125;
  </script>

其它的知识点可以通过以下来进行补充,自己就不打出来了

(96条消息) 【Promise】入门-同步回调-异步回调-JS中的异常error处理-Promis的理解和使用-基本使用-链式调用-七个关键问题_YK菌的博客-CSDN博客

promise手写

整体框架

定义整体框架,以及能够实现基本的resolve和reject方法

function Promise(executor) &#123;

  const self = this;
  // 这里下面也需要写成self的形式,因为不然之后this的指向会出问题,反正this这里需要再好好看一下
  self.promiseState = 'pending';
  self.promiseResult;

  // 在resolve和reject中主要做的就是转换promise状态 以及获取到值
  function resolve(value) &#123;
    // 因为resolve是直接调用,不是通过对象调用的,所以注意this指向
    self.promiseState = 'fulfilled';
    self.promiseResult = value;
  &#125;
  function reject(value) &#123;
    self.promiseState = 'rejected';
    self.promiseResult = value;
  &#125;
  executor(resolve,reject);
&#125;

Promise.prototype.then = function(onResolved,onRejected) &#123;
  // 这里需要根据promise对象的状态决定执行哪个方法
  // 如果是成功的 执行onResolved
  // 说明一下为什么这里可以使用this来进行获取
  // 因为这里的then是通过对象调用的 this指向就是promise实例 它本身有promiseState这个属性
  if(this.promiseState === 'fulfilled') &#123;
    onResolved(this.promiseResult);
  &#125;
  // 否则执行onRejected
  if(this.promiseState === 'rejected') &#123;
    onRejected(this.promiseResult);
  &#125;
&#125;

抛出错误

接下来是捕获到错误 改变状态不止通过resolve和reject这两种方法

// 在Promise构造函数中添加
// 对于抛出异常的情况,需要在executor中进行捕获
  try&#123;
    executor(resolve,reject);
  &#125;
  catch(e) &#123;
    reject(e)
  &#125;

// 控制台打印出p 可以看到状态已经换成了rejected
Promise &#123;promiseState: 'rejected', promiseResult: 'error'&#125;
promiseResult:"error"
promiseState:"rejected"
[[Prototype]]:Object

修改一次状态

完成状态只能够修改一次,就是如果状态已经修改了,则不能够进行修改了,否则可以修改一次

function resolve(value) &#123;
    // 如果只能够修改一次的话,需要判断此时的状态是否是pending 是pending才能够修改,否则不能够修改
    if(self.promiseState !== 'pending') return;
    // 因为resolve是直接调用,不是通过对象调用的,所以注意this指向
    self.promiseState = 'fulfilled';
    self.promiseResult = value;
&#125;

异步then实现

异步任务then的实现 当promise中是异步任务时,then也需要在状态改变之后才能执行指定的回调

// 需要在then方法中添加
// 如果promise中是异步的情况,then执行时状态还是在pending时
  // 所以将指定回调保存下来,在状态改变时进行调用即可
  // 保存到外部是非常不安全的,保存到自身比较好
  if(this.promiseState === 'pending') &#123;
    this.callback.push(&#123;
      onResolved,
      onRejected
    &#125;);
  &#125;
// 在Promise构造函数中添加
self.callback = [];

// 在resolve和reject函数中对应添加
// 执行resolve时肯定状态是确定转变为成功了 
// 那么如果这时有callback回调函数 则需要执行
if(self.callback.onRejected) &#123;
    self.callback.forEach(item => &#123;
        item.onRejected(value);
    &#125;)
&#125;

同步then返回结果

then的返回结果由里面的onResolve或onReject回调函数的执行结果决定

规则:

1、如果onResolve或onReject回调函数的执行结果返回非promise类型的数据

then的返回结果为成功的promise

2、如果onResolve或onReject回调函数的执行结果返回promise类型的数据

then的返回结果就是onResolve或onReject回调函数的执行结果

// then方法里面添加
const self = this;

if(self.promiseState === 'fulfilled') &#123;
    // 对抛出异常的情况需要在resolve中进行捕获
    try&#123;
        // 这里需要获取到成功的回调函数的结果
        // 观察它是否是promise
        let res = onResolved(self.promiseResult);
        if(res instanceof Promise) &#123;
            // 如果是promise对象 那它一定有自己的then方法,可以通过他自己的resolve或者reject来得到返回promise的状态改变
            res.then(v => &#123;resolve(v)&#125;, r => &#123;reject(r)&#125;);
        &#125; else &#123;
            // 如果是非promise 将该返回的promise对象的状态转变为成功即可,且值为回调函数返回的值
            resolve(res);
        &#125;
    &#125; catch(e) &#123;
        reject(e);
    &#125;
&#125;

异步then返回结果

if(this.promiseState === 'pending') &#123;
    this.callback.push(&#123;
        // 之前是直接push原来的onResolve即可,但是现在是异步的,需要对原来的函数改变一下
        onResolved: function () &#123;
            try&#123;
                let res = onResolved(self.promiseResult);
                if(res instanceof Promise) &#123;
                    res.then(
                        v => resolve(v),
                        r => reject(r)
                    );
                &#125; else &#123;
                    resolve(res);
                &#125;
            &#125; catch(e) &#123;
                reject(e);
            &#125;
        &#125;,

then优化与完善

可以看到之前的代码中有很多的try catch,其中它们内部的函数都差不多,只不过有个onResolved或者onRejected的区别,所以可以对该代码进行封装即可

// then方法的优化与完善
  function callback(type) &#123;
      try&#123;
          // 这里需要获取到成功的回调函数的结果
          // 观察它是否是promise
          let res = type(self.promiseResult);
          if(res instanceof Promise) &#123;
              // 如果是promise对象 那它一定有自己的then方法,可以通过他自己的resolve或者reject来得到返回promise的状态改变
              res.then(v => &#123;resolve(v)&#125;, r => &#123;reject(r)&#125;);
          &#125; else &#123;
              // 如果是非promise 将该返回的promise对象的状态转变为成功即可,且值为回调函数返回的值
              resolve(res);
          &#125;
      &#125; catch(e) &#123;
          reject(e);
      &#125;
  &#125;

// 在下方使用的时候就是这样的使用方法
 if(self.promiseState === 'fulfilled') &#123;
     callback(onResolved);
 &#125;

catch方法与异常穿透

catch其实是js中的语法糖,依旧使用的是then的代码,只不过resolve传入undefined即可

异常穿透:在then的链式调用中,中间任务不需要对失败的结果进行处理,只需要在最后加一个catch方法去处理失败的结果。

  // 解决异常穿透 在then方法前面添加
  // 异常穿透的中途如果有失败的话,但是可以then方法里面只写了onResolved方法
  // 没有写onRejected方法,那么如果此时发生了错误,由于没有定义onRejected
  // 就会产生undefined,会报错,故需要自己检测 如果没有定义需要自己定义
  if(typeof onRejected !== 'function') &#123;
    onRejected = reason => &#123;
      throw reason;
    &#125;
  &#125;
  
  // 同理,onResolved也是如此
  if(typeof onResolved !== 'function') &#123;
    // 此处是简写形式,value => return value;
    onResolved = value => value;
  &#125;

Promise.prototype.catch = function(onRejected) &#123;
  return this.then(undefined,onRejected);
&#125;

Promise.resolve函数

返回的是一个成功的promise对象

Promise.resolve = function(value) &#123;
  return new Promise((resolve,reject) => &#123;
    if(value instanceof Promise) &#123;
      value.then(
        v => resolve(v),
        r => reject(r)
      );
    &#125; else &#123;
      resolve(value);
    &#125;
  &#125;)
&#125;

Promise.reject

返回一个失败的promise对象

Promise.reject = function(value) &#123;
  return new Promise((resolve,reject) => &#123;
    reject(value);
  &#125;);
&#125;

Promise.all

Promise.all = function(promises) &#123;
  // 成功的个数
  let count = 0;
  // 成功的结果
  let arr = [];
  return new Promise((resolve,reject) => &#123;
    for(let i = 0;i<promises.length;i++) &#123;
      promises[i].then(
        v => &#123;
          count++;
          arr[i] = v;
          if(count === promises.length) &#123;
            resolve(arr);
          &#125;
        &#125;,
        r => reject(r)
      );
    &#125;
  &#125;)
&#125;

Promise.race

Promise.race = function(promises) &#123;
  return new Promise((resolve,reject) => &#123;
    for(let i = 0;i<promises.length;i++) &#123;
      promises[i].then(
        v => resolve(v),
        r => reject(r)
      );
    &#125;
  &#125;)
&#125;

回调函数异步执行

let p = new Promise((resolve,reject) => &#123;
    resolve('ok');
    console.log(111);
&#125;)
p.then(
    v => console.log(222)
)
console.log(333);
// 输出:111 333 222
// 因为then里面指定的回调是异步执行的,等同步执行完毕之后它才会执行

解决:

在then方法和callback方法中的立即执行加上一个定时器

// 加上定时器是因为要使得then指定的回调是异步执行的
setTimeout(callback(onResolved),0);

async与await

async函数

1、async函数的返回值是promise对象

2、promise对象的结果由async函数执行的返回值决定

async有点像then方法,它的返回规则与then方法中的规则一致

如果async函数中返回的是非promise 则成功,且值为该值

否则返回该promise的执行结果

await表达式

await返回的是promise成功的值,功能主要是对他后面接的promise对象的成功值进行获取。

1、await右侧的表达式一般为 promise 对象, 但也可以是其它的值

2、如果表达式是 promise 对象, await 返回的是 promise 成功的值

3、如果表达式是其它值, 直接将此值作为 await 的返回值

ps:

await 必须写在 async 函数中, 但 async 函数中可以没有 await

如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理

例子

使用async和await对多个文件值进行获取

// 对多个文件进行值的获取
// 还是使用nodejs的fs读取模块

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

// 声明main方法为一个异步方法
// 在里面使用await接收文件结果
async function main() &#123;
  try&#123;
    let data1 = await mineReadFile('./content.txt');
    data1 = data1.toString();
    let data2 = await mineReadFile('./content2.txt');
    data2 = data2.toString();
    let data3 = await mineReadFile('./content3.txt');
    data3 = data3.toString();

    console.log(data1 + data2 + data3);
  &#125;
  catch(e) &#123;
    console.log(e);
  &#125;
&#125;

main()

使用async和await进行Ajax发送请求

需要将ajax封装成一个Promise对象(这一步就是axios做的事情,它是基于promise的基本方式对ajax进行封装的包)