浅谈Promise的原理和应用

440 阅读7分钟

promise有什么用

先导知识:同步回调和异步回调

  1. 同步回调
    立即执行,完全执行完了才结束,不会放入回调队列中。
    举个例子,我们用forEach遍历一个数组。

    const arr = [1,2,3];
    arr.forEach( item =>{
        // 遍历的回调函数
        console.log('遍历开始');// 先打印
    })
    console.log('遍历结束');// 后打印
    

    也就是说我的回调函数,是直接执行,而没有放在待执行栈里的。

  2. 异步回调
    不会立即执行,会放到回调队列这种将来执行(定时器,ajax,Promise的成功失败回调等)...

    const arr = [1,2,3];
    // 异步任务是立即执行的(即,setTimeout函数)
    setTimeout(()=>{
        //异步任务的回调函数
        //同步任务完成之后才执行
        console.log('setTimeout回调')// 同步任务完成后打印
    },1000)
    console.log('结束');// 同步任务的最后一个语句,先打印
    

    在一个异步的回调里,解析到setTimeout这个函数的时候,会把里面的回调函数(也就是console.log('setTimeout回调')这行代码)压入待执行栈里,然后继续执行下一行,也就是第二个console。

    待其他代码都执行好后,在从待执行栈里拿语句执行(console.log('setTimeout回调'))

避免回调地狱

promise是一个代理对象,他解决的是异步的问题,他允许你为异步操作的成功和失败分别绑定不同的方法。

我们刚才解释了异步回调,我们发现,如果我们想要对异步回调返回的数据进行操作,就需要嵌套一层异步回调,当嵌套层数多的时候,就会发生我们所说的回调地狱,在代码比较复杂的情况下,可读性会非常差:

    fn1((result)=>{
        fn2(result,(newResult)=>{
            // fn2依赖fn1回调产生得到的数据result
            // 并处理把它作为fn2的回调函数形参传入
            fn3(newReault,(finalResult)>{
                // fn3依赖fn2处理得到的newResult
                // 并把它传入fn3的回调函数形参finalResult
                ...
            })
        })
    });

Promise支持链式调用,也就是它对异步回调函数的处理不需要嵌套使用,在这里我先写一个基础的应该举例,具体语法我们后续再分析。

    new Promise( (resolve,reject){
        if(...){
            resolve(value)
        }else{
            reject(reason)
        }
    }).then( (value)=>{
        // promise调用resolve回调,获得参数value
        // 把参数value参数传给新的promise对象fn1进行处理
        return fn1(value)
    }).then( (newValue)=>{
        // fn1处理value得到newValue
        // 把参数newValue参数传给新的promise对象fn2进行处理
        return fn2(newValue)
    }).then( (finalResult)=>{
        // fn2处理 newValue 得到 finalResult
        // 把参数 finalResult 参数传给新的promise对象fn3进行处
        return fn3(finalResult)
    });

我们发现,代码变成了链式的,更加的清晰增加可读性。

更加灵活的回调

  1. 旧的纯回调方式
    当我们用纯回调的方式处理异步函数,我们的回调函数和函数的事件是一起定义的:
    // 成功的回调
    const success = (data)=> {
        console.log('success',data);
    }
    
    // 失败的回调
    const fail = (data)=> {
        console.log('fail',data);
    }
    
    // 纯回调异步函数
    const asyncTask = (data,success,fail)=>{
       setTimeout(()=>{
            //data是奇数还是偶数
           data % 2 == 0 ? success(data) : fail(data);
       },1000)
   }
   
   // 调用,启动异步函数之前就要指定回调函数
   asyncTask(2,success,fail);// 1秒后输出:success 2
   asyncTask(3,success,fail);// 1秒后输出:fail 3

也就是说,在调用asyncTask函数的时候,我们就已经要把成功和失败的函数定义好,即便我们现在不知道结果是成功还是失败。

  1. promise方法
    但是Promise不一样,promise支持我们在同步事件调用之前,同步时间调用异步回调还没调用以及异步回调执行完成三种状态调用函数,举个例子:
    const p = new Promise( (resolve,reject) =>{
        // setTimout立即执行,异步回调稍后执行
        setTimeout(()=>{
            // 异步函数的回调函数
            // 当前时间是奇数还是偶数
           Date.now() % 2 == 0 ? resolve : reject;
       },3000)
    })
    // setTimeout调用之后,回调函数调用之前,我们都没有必须要指定对回调函数的操作
    p.then( resolve, reject);

怎么用Promise

机制

  1. promise有三种状态
    pending:初始状态,可以转为以下两种状态(只能改变一次)
    fulfilled:成功 => 执行 onFulfilled rejected:失败 => 执行 onRejected
  2. 再创建promise示例时,传入一个处理器函数作为参数,这个函数有两个形参,分别是resolve和reject,代表处理器函数执行任务的不同结果调用的函数对象。
  3. 当异步任务执行成功,状态由pending变为resolve状态,则调用onResolved函数进行进一步操作
  4. 当异步任务执行失败,状态由pending变为reject状态,则调用onRejected函数。

语法

Promise.prototype.then(onResolved, onRejected)

@params

onResolved(可选) : promise变成resolve状态调用的回调函数

onRejected(可选) : promise变成rejected状态调用的回调函数

@return

返回一个promise对象(即可以继续调用then函数,这里称p2),如果p1的then函数里有返回值:

    返回的是值,则p2的状态pending -> resolved,val -> 返回的值。
    
    没有返回值,pending -> resolved, val -> undefined
    
    抛出异常,pennding -> rejected, reason -> 抛出的异常

p1返回的是一个promise对象:

    则p2的状态和p1返回的promise对象一样,参数是p1返回的promise对象的值一样。

***then里的回调函数是异步执行的
    const p1 = new Promise( (resolve, reject)=> {
        reject(1);
        // resolve(2);
    });
    
    // p1.then 返回一个promise对象,也就是可以进行链式调用
    p1.then( p1_val=>{
        // p1 成功的回调
        return '我是p1成功调用的返回值:'+ p1_val;
    }, p1_reason =>{
        // p1 失败的回调
        // 返回的是值,因此p1.then返回的promise对象状态变成resolved
        return '我是p1调用失败的返回值:' + p1_reason;
    }).then( p2_val=>{
        // 得到了p1异步调用的结果
        console.log(p2_val);
    })

    // -> 控制台打印 我是p1调用失败的返回值:1

Promise.prototype.catch(onRejected)

@params

onRejected:当Promise 被rejected时,被调用的一个Function。

该函数拥有一个参数:reason 即,失败的原因。

@returns

promise对象,也可以进行链式处理
    const p1 = new Promise( (resolve, reject)=> {
        reject(1);//表示把状态改成rejected,并给reject函数传递参数 1
    });
    
    // catch 捕捉rejected状态的形参 (一般称为reason)
    p1.catch( (reason)=>{
        console.log(reason);
    })
    // => 1;

Promise.all(iterable)
@params

可迭代对象,array或者string  

@return

当所有的实例都resolved,或者参数不包含promise,回调完成(resolved)

当参数中有一个失败了,则回调失败(rejected)

参数按照迭代器里promise对象的顺序排列
    const p1 = new Promise( (resolve, reject)=> {
        resolve('p1');
    });
    
    const p2 = new Promise( (resolve, reject)=> {
        resolve('p2');
    });
    
    Promise.all(['1',p1,p2]).then( vals =>{
        console.log(vals);
    }, reason =>{
        console.log(reason);
    })
    
    // -> 控制台打印 [ '1', 'p1', 'p2' ]

Promise.race(iterable)

@params

可迭代对象,array或者string

@return

返回Promise对象

一旦迭代器中的某个promise完成或拒绝,返回的promise对象状态就变成完成或拒绝
    const p1 = new Promise( (resolve, reject)=> {
        reject('p1');
    });
    
    const p2 = new Promise( (resolve, reject)=> {
        setTimeout(() => {
            reject('p2');
        }, 1000);
    });
    
    Promise.race([p1,p2]).then( val =>{
        console.log(val);
    }, reason =>{
        console.log(reason);
    })
    
    // -> 控制台打印 p1

Promise.reject(reason)

@params

传递给失败回调的参数

@return

一个状态为失败(rejected)的promise对象
    Promise.reject(new Error('failed')).catch( reason =>{
        throw reason;
    });
    
    // -> 控制台抛出我们定义的异常
    // UnhandledPromiseRejectionWarning: Error: failed

Promise.resolve(value)

params

传递给成功回调的参数

@return

一个状态为成功(resolved)的promise对象
    Promise.resolve('success').then( val  =>{
        console.log(val);
    });
    
    // -> 控制台打印 success

注意事项

状态只能改变一次

        new Promise( (resolve,reject)=>{
            // pedding 改为 resolved
            resolve('success');
            // 因为状态在上面已经改变了,因此不会在变成rejected
            reject('failed');
        }).then( val=>{
            console.log(val);
        }, reason =>{
            console.log(reason);
        })
        // -> success

手写一个AJAX请求

function myAsyncFunction(url) {
    // 返回一个promise对象
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        // 建立连接
        xhr.open("GET", url);
        // 连接成功的回调
        xhr.onload = () => resolve(xhr.responseText);
        // 连接失败的回调
        xhr.onerror = () => reject(xhr.statusText);
        // 发送请求(异步)
        xhr.send();
    });
};