Promise真的很简单!

119 阅读6分钟

同步与异步

最早JS是用来处理页面交互的,也就是DOM操作,这就造成了JS必须是只能单线程的。否则如果有两个线程同时操作了DOM该怎么办呢?所以在JS执行过程中只有一个线程执行代码。那出现耗时任务怎么办呢?JS提出了异步模式。但他依然是单线程。

同步模式:代码依次执行,怎么写的怎么执行,上一行执行完执行下一行。按序执行。存在问题:如果有一行任务很大时间很长,就会给用户一种卡死的感觉。

异步模式:不会等待任务结束再执行,而是直接开始执行下一个。最后通过回调函数解决。会有一个任务队列,调用栈空时就开始执行消息队列。

实现异步语法:回调函数——根本方案搞清楚步骤,什么完了做什么。函数作为参数,定义好了,再去执行。

Promise

Promise是ES6引入的异步编程的新解决方案。

console.dir(Promise)

image.png

语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

Promise 构造函数: Promise(excutor) {}
Promise.prototype.then 方法
Promise.prototype.catch

基本用法

//实例化 Promise 对象
const p = new Promise(function(resolve, reject) {
    setTimeout(function(){
    // let data = '数据库中的用户数据';
    // resolve(data);
    let err = '数据读取失败';
    reject(err);
    }, 1000);
});

//调用 promise 对象的 then 方法
p.then(function(value) {
    console.log(value);
    }, function(reason){
    console.error(reason);
})

生命周期

每个Promise都会经历一个短暂的生命周期:先是处于进行中(pending) 的状态,此时操作尚未完成,所以它也是未处理(unsettled)的;一旦异步操作执行结束,Promise则变为已处理(settled)的状态。

内部属性[[Promisestate]]被用来表示Promise的3种状态:“pending”、"fulfilled"及”rejected”。这个属性不暴露在Promise对象上,所以不能以编程的方式检测Promise的状态,只有当Promise的状态改变时,通过then()方法来釆取特定的行动。

Promise.then() 方法

所有Promise都有then()方法,它接受两个参数:第一个是当Promise的状态变为fulfilled时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数(fulfilment function);第二个是当Promise的状态变为rejected时。

要调用的函数,其与完成时调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数(rejection function)。

链式调用/串联Promise

每次调用then()方法或catch()方法时实际上创建并返回了另一个Promise, 只有当第一个Promise完成或被拒绝后,第二个才会被解决。

误区:嵌套使用promise then里边继续嵌套函数

Promise提供了链式调用,最大程度的避免回调嵌套,因为then方法也返回的是一个Promise对象,所以可以不断的进行.then()。

Promise对象的then方法会返回一个全新的Promise对象,后面的then方法会为上一个返回的then进行注册回调,前面的then方法中回调函数的返回值会作为后面then方法回调的参数,如果回调中返回的是Promise,那后面的then方法回调会等待它的结束。也可以catch()指定失败回调,这个catch 是指定上一个then返回的Promise的异常,不捕获第一个Promise。Promise的异常会一直传递,但是每个then的第二个参数只是捕获当前的Promise。

then返回的Promise

then方法的返回结果是由then里边的回调函数的返回结果决定的。

1、如果返回结果是非Promise的类型,返回的Promise状态为成功并且返回值是return的值,如果不写return 则undefined。

2、如果是Promise,返回的Promise的值和状态都是内部Promise的值和状态。但是这里是新的Promise。

3、抛出错误,状态失败值是抛出的值。

静态方法

Promise.resolve() // 快速转换为Promise。

Promise.reject() // 快速生成一个失败的Promise。

Promise.all([]) // 同步执行多个Promise,返回新的Promise.all()捕获全部结束的时机。

Promise.race() // 同步执行多个Promise,返回第一个结束的Promise并且终止其他。

Promise.all()方法

Promise.all()方法只接受一个参数并返回一个Promise,该参数是一个含有多个受监视Promise的可迭代对象(例如,一个数组),只有当可迭代对象中 所有Promise都被解决后返回的Promise才会被解决,只有当可迭代对象中所有Promise都被完成后返回的Promise才会被完成。

Promise.race()方法

Promise.race()方法监听多个Promise的方法稍有不同:它也接受含多个受监视Promise的可迭代对象作为唯一参数并返回一个Promise,但只要有一个Promise被解决返回的Promise就被解决,无须等到所有Promise都被完成。一旦数组中的某个Promise被完成,Promise.race()方法也会像Promise.all() 方法一样返回一个特定的Promise。

Promise应用

解决跨域:www.jianshu.com/p/e87b02128…

免费的接口:www.jianshu.com/p/f180deefd…

封装AJAX

原生的AJAX

//1. 创建对象
const xhr = new XMLHttpRequest();

//2. 初始化
xhr.open("GET", "http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=你好&bk_length=600");

//3. 发送
xhr.send();

//4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function () {
    //判断
    if (xhr.readyState === 4) {
        //判断响应状态码 200-299 表示成功
        if (xhr.status >= 200 && xhr.status < 300) {
            //表示成功
            console.log(xhr.response);
        } else {
            //如果失败
            console.error(xhr.status);
        }
    }
}

Promise封装的AJAX

const p = new Promise((resolve, reject) => {
    //1. 创建对象
    const xhr = new XMLHttpRequest();

    //2. 初始化
    xhr.open("GET", "http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=你好&bk_length=600");

    //3. 发送
    xhr.send();

    //4. 绑定事件, 处理响应结果
    xhr.onreadystatechange = function () {
        //判断
        if(xhr.readyState === 4) {
            //判断响应状态码 200-299
            if (xhr.status >= 200 && xhr.status < 300) {
                //表示成功
                resolve(xhr.response);
            } else {
                //如果失败
                reject(xhr.status);
            }
        }
    }
})    
//指定回调
p.then(function(value) {
    console.log('v成功',value);
    }, function(reason) {
        console.error('失败',reason);
})

解决回调地狱

假设现在有这样一个场景,我们根据小明的身份证号获取小明的基础信息,根据小明的基础信息的地址查询小明的家乡景区,根据景区获取景区的详细介绍。最后用基础信息和家乡介绍生成小明的自我介绍。

ES5的写法

function getIntroduce(cardId) {
    setTimeout(() => { 
        const personInfo = {
            id: cardId,
            name: '小明',
            address: '北京',
            age:19
        }
        console.log(personInfo);
        setTimeout(() => { 
            const Attractions = ['故宫', '长城', '颐和园']
            console.log(Attractions);
            setTimeout(() => { 
                const sebnceIntro = Attractions[0] + '是一个五A级景区,他的历史悠久,记载着很多。。。';
                console.log(sebnceIntro);
                const res = `我叫${personInfo.name},今年${personInfo.age}来自${personInfo.address},我的家乡有谱很多名胜古迹,比如${ Attractions.join(',') }等等,其中${ sebnceIntro }希望大家有机会常来玩`;
                console.log(res);
            }, 1000)
        }, 3000)
    }, 200)
};
getIntroduce(1)

Promise的写法

function getIntroduce(cardId) {
    const personInfo = new Promise((resolve,reject)=>{
        const info = {
            id: cardId,
            name: '小明',
            address: '北京',
            age:19
        }
            resolve({info,text:`我叫${info.name},今年${info.age}来自${info.address}。`})
        })
        const Attractions = personInfo.then(value=>{
            const list = ['故宫', '长城', '颐和园']
            return {
            text:value.text+`我的家乡有谱很多名胜古迹,比如${
                list.join(',')
            }`,
                Attractions:list
            }
        })
        const info = Attractions.then(value=>{
             const sebnceIntro = value.text + value.Attractions[0] + '是一个五A级景区,他的历史悠久,记载着很多。。。';
             return {text:value.text+`等等,其中${sebnceIntro}希望大家有机会常来玩`}
        })
        info.then(value => {
            console.log(value.text); 
        })
}
getIntroduce(1)

手写Promise

Promise的需求是构造函数,两个回调函数参数改变state,then判断状态,拿到参数,执行对应的回调,链式调用。Promise异步与多次调用处理,在cbs中值得穿透,race/all 返回Promise。

const pendding = 'pendding';
const fulfilled = 'fulfilled';
const rejected = 'rejected';
class myPromise {
    constructor(exct) {
        this.state = pendding;
        this.value = undefined
        this.reason = undefined;
        this.successCbs = [];
        this.rejectCbs = [];

        const resolve = (value) => {
            if(this.state == pendding) {
                this.state = fulfilled;
                this.value = value;
                this.successCbs.forEach(cb => cb())
            }
        } 
        const reject = (reason) => {
            if (this.state == pendding) {
                this.state = rejected;
                this.reason = reason;
                this.rejectCbs.forEach(cb=>cb())
            }
        }
        try {
            exct(resolve, reject)
        } catch (e) {
            console.log(e);
            reject(e)
        }
    };
    then(successCb= value => value, rejectCb = reason => { throw reason }) {
        return new myPromise((resolve , reject) => {
            if (this.state == fulfilled) {
                try {
                    let res = successCb(this.value);
                    resolvePromise(res, resolve, reject);
                } catch (e) {
                    console.log(e);
                    reject(e)
                }
            } else if (this.state == rejected) {
                try {
                    let res = rejectCb(this.reason);
                    resolvePromise(res, resolve, reject)
                } catch (e) {
                    console.log(e);
                    reject(e)
                }
            } else {
                this.rejectCbs.push(() => {
                    try {
                        let res = rejectCb(this.value);
                        resolvePromise(res, resolve, reject)
                    } catch (e) {
                        console.log(e);
                        reject(e)
                    }
                });
                this.successCbs.push(() => {
                    try {
                        let res = successCb(this.value);
                        resolvePromise(res, resolve, reject)
                    } catch (e) {
                        console.log(e)
                        reject(e)
                    }
                })
            }
        })
    };
    static all(array) {
        let result = [];
        let index = 0;
        return new myPromise((resolve, reject) => {
            debugger;
            let addResult = (key,value) => {
                result[key] = value;
                index++;
                if (index == array.length) {
                    debugger;
                    resolve(result)
                } 
            }
            for (let i = 0; i < array.length; i++) {
                debugger;
                let current = array[i];
                if (current instanceof myPromise) {
                    current.then(res => addResult(i,res),reason=>reject(reason))
                } else {
                    addResult(i,current)
                }
            }
        })
    }
    static resolve(target) {
        if (value instanceof myPromise) return value;
        return new myPromise(v=>v(value))
    }
    static race(array) {
        return new myPromise((resolve, reject) => {
            for (let i = 0; i < array.length; i++) {
                let current = array[i];
                if (current instanceof myPromise) {
                    current.then(res => resolve(res),reason=>reject(reason));
                } else {
                    resolve(current)
                }
            }
        })
    }
};
function resolvePromise(value, resolve, reject) {
    if (value instanceof myPromise) return value.then(resolve, reject);
    return resolve(value)
}

// test 
// let p = new myPromise((res, rej) => {
//     res('成功')
// })
// p.then((value) => {
//     console.log(value)
//     return new myPromise((v) =>  v('111') )
// }).then((v) => {
//     setTimeout(()=>console.log(2, v), 3000)
// }, (v) => {
//     console.log('2失败', v)
// })

let pall = myPromise.all([
    '1',
    new myPromise((s1) => {
        setTimeout(() => s1('2'), 1000);
    }),
    '3',
    new myPromise((s) => { s('4') }),
    '5'
]);
let prace = myPromise.race([
    new myPromise((s1) => {
        setTimeout(() => s1('2'), 0);
    }),
    new myPromise((s1) => {
        setTimeout(() => s1('3'), 1000);
    }),
    // new myPromise((s) => { s('4') }),
]);
console.log(pall);
prace.then((res) => {
    console.log('xxxxx',res);
})