08月08, 2022

JS进阶(10)--Promise(2)--Promise基础

Promise基础

回调地狱

在编写js异步代码的时候,我们经常会遇到,必须要等到得到数据或者反馈之后,才能进行下一步的操作,不然,整个代码就不能执行,也就是说,我们的代码,必须要放在异步函数的中才行,比如下面的代码:

let users = null;

Mock.mock(/users/,"get",function(){
  return Mock.mock({
    "users|10-15":[{
      "username":"@cname()",
      "sex|1":["男","女"],
      "tel":/^1[3-9]\d{9}$/,
      "score|1-100":1
    }]
  }).users
})
let xhr = new XMLHttpRequest();
xhr.open("get","/users",false);
xhr.send();
xhr.onreadystatechange = function(){
  if(xhr.readyState === 4 && xhr.status === 200){
    let txt = xhr.responseText; 
    users = JSON.parse(txt);

    //如果数据是ajax异步获取的
    //那么处理数据的操作,就必须放在ajax的异步操作中
    setHTML(users);
  }
}

function setHTML(users){
  for (let i = 0; i < users.length; i++) {
    let user = users[i];
    let str = `<tr>
        <td>${user.username}</td>
        <td>${user.sex}</td>
        <td>${user.tel}</td>
        <td>${user.score}</td>
      </tr>`
    mybody.innerHTML += str
  }
}

//这里直接调用会报错...
//因为数据是通过ajax异步获取的,这里是同步代码
//users在这个时候还没有值
// setHTML(users);

这是我们以前已经写过的一个代码,很明显,如果在ajax回调函数以外不能获取数据,只能把操作放入到回调函数中进行。

当然,这么看你觉得还行,但是如果这个操作之后,还需要再进行下一步的异步操作呢?

比如,我们模拟一个求职的过程,当你发出求职信,等待企业的回复,如果拒绝了就继续再投出下一家的求职信,如果再拒绝了,再投...

function sendMessage(company,onFulfilled,onReject) { 
  console.log(`向 ${company} 公司提交了求职信!`);
  console.log("......等待回复中......")
  setTimeout(function () { 
    if (Math.random() >= 0.1) {
      if (Math.random() >= 0.5) {
        onFulfilled({
          pass: true,
          msg: `恭喜你,通过了${company}公司的面试`
        })
      }
      else { 
        onFulfilled({
          pass: false,
          msg: `对不起,${company}拒绝了你的面试申请`
        })
      }
    }
    else { 
      if (Math.random() >= 0.5) {
        onReject({
          code: 1,
          msg: "你的网络出错,发不了求职信了"
        })
      }
      else { 
        onReject({
          code: 2,
          msg: "你的征信有问题,直接被拉黑"
        })
      }
    }
  }, 1000);
}

sendMessage("华为", (reply) => { 
  if (reply.pass) {
    console.log(reply.msg);
  }
  else { 
    sendMessage("小米", (reply) => { 
      if (reply.pass) {
        console.log(reply.msg);
      }
      else { 
        sendMessage("京东", (reply) => { 
          if (reply.pass) {
            console.log(reply.msg);
          }
          else { 
            sendMessage("天美", (reply) => { 
              if (reply.pass) {
                console.log(reply.msg);
              }
              else { 
                console.log("...休息一下")
              }
            }, (err) => { 
              console.log(err.msg);
            })
          }
        }, (err) => { 
          console.log(err.msg);
        })
      }
    }, (err) => { 
      console.log(err.msg);
    })
  }
}, (err) => { 
  console.log(err.msg);
})

这一层一层的回调嵌套,形成了传说中的「回调地狱 callback hell

要解决这样的问题,需要Promise出马

Promise规范

Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一

这套规范最早诞生于前端社区,规范名称为Promise A+

该规范出现后,立即得到了很多开发者的响应

Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象,该对象称之为Promise对象,也叫做任务对象
  2. 每个任务对象,都应该有两个阶段、三个状态

根据常理,它们之间存在以下逻辑:

  • 任务总是从未决阶段变到已决阶段,无法逆行
  • 任务总是从挂起状态变到完成或失败状态,无法逆行
  • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变
  1. 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected

Promise API

ES6提供了一套API,实现了Promise A+规范

基本使用如下:

// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
  // 任务的具体执行流程,该函数会立即被执行
  // 意思是这里的代码是同步代码
  // 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
  // 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});

pro.then(
  (data) => {
    // onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
  },
  (reason) => {
    // onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
  }
);

要特别注意一个问题,new Promise() 中直接执行的代码是同步代码,只有调用then之后执行的代码,才是异步代码

将求职的代码改为Promise

function sendMessage(company) { 
  return new Promise((resolve, reject) => {
    console.log(`向 ${company} 公司提交了求职信!`);
    console.log("......等待回复中......")
    setTimeout(function () { 
      if (Math.random() >= 0.1) {
        if (Math.random() >= 0.5) {
          resolve({
            pass: true,
            msg: `恭喜你,通过了${company}公司的面试`
          })
        }
        else { 
          resolve({
            pass: false,
            msg: `对不起,${company}拒绝了你的面试申请`
          })
        }
      }
      else { 
        if (Math.random() >= 0.5) {
          reject({
            code: 1,
            msg: "你的网络出错,发不了求职信了"
          })
        }
        else { 
          reject({
            code: 2,
            msg: "你的征信有问题,直接被拉黑"
          })
        }
      }
    }, 1000);
  })
}

简单调用:

let pro = sendMessage("华为");

pro.then((data) => { 
  if (data.pass) {
    console.log(data.msg);
  }
  else { 
    console.log(data.msg);
  }
}, (reason) => { 
  console.log(reason.msg);
})

ajax封装为Promise

原生js封装

const ajaxPromise =  param => {
  return new Promise((resovle, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.open(param.type || "get", param.url, true);
    xhr.send(param.data || null);

    xhr.onreadystatechange = () => {
     if(xhr.readyState === 4){
      if(xhr.status === 200){
        resovle(JSON.parse(xhr.responseText));
      } else{
        reject(JSON.parse(xhr.responseText));
      }
     }
    }
  })
}

JQuery封装

const ajaxPromise = param => {
  return new Promise((resolve,reject)=>{
    $.ajax({
      "url":param.url,
      "type":param.type || "get",
      "async":param.async || true,
      "data":param.data || "",
      dataType:param.data || "json",
      "success": res => {
        resolve(res);
      },
      "error": err => {
        reject(err);
      }
    })
  })
}

调用

ajaxPromise({
  url:"/users",
}).then(data=>{
  setHTML(data);
})

本文链接:http://www.yanhongzhi.com/post/js_ap_23.html

-- EOF --

Comments