异步处理-Promise的基本使用

344 阅读3分钟

应用场景

处理异步场景

如何理解Promise?

Promise不是消除回调,而是让回调变得可控。

如何使用?

const pro = new Promise((resolve, reject) => {
    // 未决阶段

    // 通过调用resolve函数,将Promise推向已决状态的resolved状态

    // 通过调用reject函数,将Promise推向已决状态的rejected状态

    // resolve和reject均可传递最多一个参数,表示推向状态的数据
})

举例说明:垦哥向女神发送表白短信 ES5版

// 垦哥向女神发送表白短信
/**
 * 
 * @param {*} god 女神名
 * @param {*} callback 有回应之后要执行的回调函数
 */
function biaobai(god, callback) {
    console.log(`垦哥向女神${god}发送了表白短信`);

    setTimeout(() => {
        if (Math.random() < 0.1) {
            // 女神同意啦
            callback(true);
        } else {
            // 女神拒绝啦
            callback(false);
        }
    }, 1000)
}

Promise版

const pro = new Promise((resolve, reject) => {
    console.log(`垦哥向女神1发送了表白短信`);

    setTimeout(() => {
        if (Math.random() < 0.1) {
            // 女神同意啦
            resolve(true); // 通过resolve函数推向已决状态
        } else {
            // 女神拒绝啦
            resolve(false); // 通过resolve函数推向已决状态
        }
    }, 1000)
})

举例说明:发送ajax请求

Promise版

const pro = new Promise((resolve, reject) => {
    ajax({
        url:'http://abc.json',
        success: function(data){
             resolve(data); // 请求成功之后推向已决阶段的已解决状态
        },
        error: function(err){ // 请求失败之后推向已决阶段的已拒绝状态
            reject(err); 
        }
    })
})

完整用法:

const pro = new Promise((resolve, reject) => {
    // 未决阶段
    // 通过调用resolve函数,将Promise推向已决状态的resolved状态
    // 通过调用reject函数,将Promise推向已决状态的rejected状态
    // resolve和reject均可传递最多一个参数,表示推向状态的数据
})

/**
 * 后续处理
 * 传两个参数(两个函数)
 */
pro.then(data => {
    // 这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
    // 如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
    // data为状态数据
}, err => { 
    // 这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
    // 如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
    // err为状态数据
})

举例:

const pro = new Promise((resolve, reject) => {
    console.log("未决阶段")
    resolve(123);
})

pro.then(data => {
    // 此时pro的状态是已决状态的resolved
    console.log(data);
})

// 输出 未决阶段  123
const pro = new Promise((resolve, reject) => {
    console.log("未决阶段");
    setTimeout(() => {
        if (Math.random() < 0.5) {
            resolve(123);
        } else {
            reject(new Error('报错啦'));
        }
    }, 3000)

})

pro.then(data => {
    // 此时pro的状态是已决状态的resolved
    console.log(data);
}, err => {
    console.log(err);
})

如何注册多个thenable?

pro.then(data => {
    // 此时pro的状态是已决状态的resolved
    console.log(data);
})
pro.then(data => {
    // 此时pro的状态是已决状态的resolved
    console.log(data);
})
pro.then(data => {
    // 此时pro的状态是已决状态的resolved
    console.log(data);
})

如何封装Promise?

  1. 举例:垦哥给女生翠花发送表白短信

    function biaobai(god) {
        return new Promise((resolve, reject) => {
            console.log(`垦哥向女神${god}发送了表白短信`);
    
            setTimeout(() => {
                if (Math.random() < 0.1) {
                    // 女神同意啦
                    resolve(true);
                } else {
                    // 女神拒绝啦
                    resolve(false); 
                }
            }, 2000)
        })
    }
    
    
    biaobai('翠花').then(result => {
        console.log(result);
    })
    

    说明:

    • 后续处理全是通过P romise,不需要再传回调函数。
    • 没有消除回调,只是把回调变得可控。
  2. 举例:封装Ajax

     function ajax(obj) {
         return new Promise((resolve, reject) => {
             //指定提交方式的默认值
             obj.type = obj.type || "get";
             //设置是否异步,默认为true(异步)
             obj.async = obj.async || true;
             //设置数据的默认值
             obj.data = obj.data || null;
             // 根据不同的浏览器创建XHR对象
             let xhr = null;
             if (window.XMLHttpRequest) {
                 // 非IE浏览器
                 xhr = new XMLHttpRequest();
             } else {
                 // IE浏览器
                 xhr = new ActiveXObject("Microsoft.XMLHTTP");
             }
             // 区分get和post,发送HTTP请求
             if (obj.type === "post") {
                 xhr.open(obj.type, obj.url, obj.async);
                 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                 let data = toData(obj.data);
                 xhr.send(data);
             } else {
                 let url = obj.url + "?" + toData(obj.data);
                 xhr.open(obj.type, url, obj.async);
                 xhr.send();
             }
             // 接收返回过来的数据
             xhr.onreadystatechange = function () {
                 if (xhr.readyState === 4) {
                     if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                         resolve(JSON.parse(xhr.responseText))
                     } else {
                         reject(xhr.status)
                     }
                 }
             }
         })
     }
     
     // 调用ajax函数
     ajax({
         url: 'http://abc.json'
     }).then(resp => {
         console.log(resp);
     }, err => {
         console.log(err);
     })
    

细节

  1. 未决阶段的处理函数是同步的,会立即执行

    const pro = new Promise((resolve, reject) => {
        console.log("a");
    })
    console.log("b");
    
    // 输出 a b 
    
  2. thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的是微队列

    举例说明:

    const pro = new Promise((resolve, reject) => {
        console.log("a");
        resolve(1);
        console.log("b");
    })
    pro.then(data => {
        console.log(data)
    })
    console.log("c");
    
    // 输出 a b c 1
    
    • thenable和catchable函数是异步的,所以会把这些函数加入到事件队列
    • 同步函数执行完,再执行事件队列里的函数
    • 注册then的时候已经是resolved了,就算是resolved了,立即执行了,但由于她是异步函数,它也要加入到事件队列,只不过是立即加入到事件队列而已

    举例说明:

    const pro = new Promise((resolve, reject) => {
        console.log("a");
        resolve(1);
        setTimeout(() => {
            console.log("b");
        }, 0);
    })
    pro.then(data => {
        console.log(data)
    })
    console.log("c");
    
    // 输出 a c 1 b
    
    1. 首先创建Promise,马上执行同步代码,输出a;
    2. 然后状态推向resolved;
    3. console.log("b") 加入到宏队列;
    4. 注册then事件,promise已经是resolved状态,所以console.log(data)立即推向微队列;
    5. 同步代码输出c;
    6. 同步代码运行完之后,先运行微队列,再运行宏队列;
    7. 最后结果为 a c 1 b。
  3. pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数

    pro.then(data => {
        console.log(data)
    })
    
    pro.catch(err => {
        console.log(err)
    })
    
  4. 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获

    const pro = new Promise((resolve, reject) => {
        throw new Error('报错');
    })
    pro.then(data => {
        console.log(data)
    })
    
    pro.catch(err => {
        console.log(err)
    })
    // 输出错误,然后报错
    

  5. 一旦状态推向了已决阶段,无法再对状态做任何更改

    const pro = new Promise((resolve, reject) => {
        resolve(1);
        reject(2); // 无效
        resolve(3); // 无效
        reject(4); // 无效
    })
    

    最终状态一定是resolved,后面的代码是无效的。状态是不可逆的。

  6. Promise没有消除回调,只是让回调变得可控

总结概述

Promise构造函数的参数是一个函数,这个函数只管把一个状态如何从未决推向已决。剩下的事情是后续处理,后续处理是通过注册then或者catch来进行处理。它把推向和处理是彻底分开的。