Promise

错误之处,欢迎指正。


1. 简介

在学习Promise之前,需要搞清楚JavaScript的异步机制,了解事件循环机制以及事件队列。
Promise出现之前,我们都是使用回调函数的方式来进行异步操作,随着前端功能越来越复杂,就可能出现回调地狱(当前的异步操作需要等待之前的异步操作完成才能执行);回调也不能良好的处理有联系的异步操作,例如某个异步操作需要等待多个异步操作完成。
PromiseES6中提出的异步解决方案,本质上Promise是一个构造函数,通过new Promise()的形式来创建一个实例。

2. Promise

  1. 两个阶段
  • unsettled 未决阶段
  • settled 已决阶段
  1. 三种状态
  • pending 等待状态
  • resolved 成功状态
  • rejected 失败状态
  1. 两个过程
  • resolve过程
    pendingresolved
  • reject过程
    pendingrejected (代码执行错误和抛出错误,都会进入`rejected·状态)
  1. Promise基础用法
    Promise并没有消除回调,而是使用了特定的模式来让回调可控,从而解决回调地狱等问题。
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (Math.random() > 0.1) {
            resolve('没中奖');
        } else {
            resolve('中奖了');
        }
    }, 100);
})
pro.then(data => {
    console.log(data);
})
pro.catch(err => {
    console.log(err);
})

注意:在未决阶段的处理函数中,如果发生了未捕获的错误,会将状态推向rejected

  1. Promise串联
    通过串联这种方式,良好的解决了回调地狱的嵌套问题。
    这里要记住,Promise中,无论是then方法还是catch方法,返回值都是一个全新的Promise,因此才产生了Promise串联模式。
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('123')
    }, 1000);
})
const pro2 = pro.then(data => {
    console.log(data); //123
    return 456
})
pro2.then(data => {
    console.log(data); //456
})
console.log(pro2); //pending

如果pro.then中抛出了错误,那么pro2rejected状态;如果没有抛出错误,则把pro.then中的函数返回值作为resolved的传递值传给pro2

const pro1 = new Promise((resolve, reject) => {
    resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve(2);
})
const pro3 = pro1.then(data => {
    return pro2;
})
pro3.then(data => {
    console.log(data); //2
})

如果Promise的处理函数返回的是一个Promise,那么新的Promise要和返回的Promise状态和数据保持一致。例如上述代码中,pro1的后续处理返回的是pro2,那么pro3就要和pro2的状态以及数据保持一致,因此返回了2

  1. finally函数
const pro = new Promise((resolve, reject) => {
    resolve(true)
})
pro.then(data => {
    console.log(data);
})
pro.finally(() => { //没有参数
    console.log('pro执行完毕');
})

Promise达到已决状态就会执行函数finally【ES2018】。

  1. resolvereject静态方法
const pro = Promise.resolve(1);
pro.then(data => {
    console.log(data); //1
})
const pro2 = Promise.reject('error');
pro2.catch(message => {
    console.log(message); //error
})

Promise还提供了两个静态方法resolvereject,这两个方法都会直接返回一个新的Promise
特殊情况:

const pro = new Promise(resolve => {
    resolve(1);
})
const pro2 = Promise.resolve(pro);
pro2.then(data => {
    console.log(data);  //1
})
console.log(pro === pro2); //true

如果Promise.resolve传递的参数是一个Promise,那么直接返回被传递的Promise。(谷歌浏览器)

  1. all
const pro1 = new Promise(resolve => {
    resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve('error');
})
const arr = [pro1, pro2];
const newPro = Promise.all(arr);
newPro.then(data => {
    console.log(data);  //[1, 'error']
})

all方法接收一个由多个Promise组成的可迭代对象,返回一个新的Promise对象,只有当参数中的所有Promiseresolved状态时,该方法返回的新的Promise才会为resolved状态,只要有一个不是resolved状态,那么新的Promise就为rejected状态;resolved状态下,新的Promise的数据是参数中Promise数据组成的数组,rejected状态下,新的Promise的数据是参数中rejected状态的Promise数据。

  1. race
const pro1 = new Promise((resolve, reject) => {
    reject(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve('error');
})
const arr = [pro1, pro2];
const newPro = Promise.race(arr);
newPro.then(data => {
    console.log(data);  
}, message => {
    console.log(message); //1
})

race方法与all方法类似,不同的是,只要参数中有一个为已决状态状态,那么race返回的Promise就是已决状态,并且和参数中的状态保持一致。

3. 在setTimout里执行Promise

setTimeout在宏队列,relovereject在微队列,微队列先执行,宏队列后执行。

console.log(1);
setTimeout(() => {
    console.log(3)
}, 0);
console.log(2);

上述代码最终输出结果为123;因为JavaScript语言的执行机制是先执行同步代码,同步代码执行完毕执行异步代码。

console.log(1);
setTimeout(() => {
    const pro = new Promise((resolve, reject) => {
        console.log(3);
        resolve(4);
    }).then(data => {
        console.log(data);
    })
}, 1000);
console.log(2);

上述代码最终输出结果为1234;先输出1,然后定时器进入宏队列,然后输出2,此时开始执行异步代码,微队列为空,执行宏队列里面的定时器,定义pro是同步代码,输出3,然后resolve进入微队列,同步代码执行完毕,执行异步代码resolve,输出4

4. 手写Promise

  1. 涉及到的知识点:ClassSymbol、立即执行函数、高阶函数。
  2. 大体步骤
  • 首先要实现Promise的状态和数据的改变
  • 其次是推向已决状态的实现
  • 然后是串联实现(比较难的一部分)
  • 最后是实现Promise的静态方法
  1. 代码
/*
1. 初始化,pending状态以及值的设定
2. 执行pending状态的处理函数
3. 在构造器中定义resolve和reject函数
4. 捕获pending状态处理函数的异常,如果有错误,直接推向reject
5. 定义状态改变函数(将未决推向已决状态的函数)
6. then、catch处理函数定义,如果此时是未决状态,就把处理函数传递过来的参数存放至执行队列;如果是已决状态,就直接执行。
*/
const myPromise = (() => {
    const PENDING = 'pending',
        RESOLVED = "resolved",
        REJECTED = "rejected",
        PromiseValue = Symbol('PromiseValue'), 
        PromiseStatus = Symbol('PromiseStatus'),
        changeStatus = Symbol('changeStatus'), 
        handle = Symbol('handle'),
        thenables = Symbol('thenables'),
        catchables = Symbol('catchables'),
        linkPromise = Symbol('linkPromise');
    return class myPromise {
        /**
         * 
         * @param {*} executor pending状态下的处理函数 
         */
        constructor(executor) {
            this[PromiseStatus] = PENDING;  //初始为pending状态
            this[PromiseValue] = undefined; //此时没有数据
            this[thenables] = [];           //then执行队列
            this[catchables] = [];          //catch执行队列

            const resolve = data => {
                this[changeStatus](RESOLVED, data, this[thenables]);
            } //resolve函数
            const reject = message => {
                this[changeStatus](REJECTED, message, this[catchables]);
            } //reject函数

            try {
                executor(resolve, reject); //执行处理函数
            } catch (error) { //错误状态推向rejected
                reject(error)
            }
        }
        /**
         * 
         * @param {*} status 状态
         * @param {*} value 数据
         */
        [changeStatus](status, value, queue) {  //推向已决状态
            if (this[PromiseStatus] !== PENDING) {
                return; //如果不是未决状态,直接返回不做处理
            }
            this[PromiseStatus] = status;  //改变状态
            this[PromiseValue] = value;    //改变数据
            queue.forEach(handler => {
                handler(this[PromiseValue]);  //执行队列后续处理执行
            });
        }

        then(thenable, catchable) {  //后续处理
            return this[linkPromise](thenable, catchable);
        }
        catch (catchable) {   //后续处理
            return this[linkPromise](undefined, catchable);
        }
        [handle](status, handler, queue) {
            if (this[PromiseStatus] === status) {
                setTimeout(() => {
                    handler(this[PromiseValue]);
                }, 0);
            } else {
                queue.push(handler);
            }
        }
        [linkPromise](thenable, catchable) {  //串联的实现
            function executor(data, exec, resolve, reject) {
                try {
                    const result = exec(data);
                    if (result instanceof myPromise) {
                        result.then(data2 => {
                            resolve(data2);
                        }, err => {
                            reject(err);
                        })
                    } else {
                        resolve(result)
                    }
                } catch (error) {
                    reject(error)
                }
            }
            return new myPromise((resolve, reject) => {
                this[handle](RESOLVED, data => {
                    if (typeof thenable !== 'function') {
                        resolve(data);
                        return;
                    }
                    executor(data, thenable, resolve, reject)
                }, this[thenables])
                this[handle](REJECTED, message => {
                    if (typeof thenable !== 'function') {
                        reject(message);
                        return;
                    }
                    executor(message, catchable, resolve, reject)
                }, this[catchables])
            })
        }
        static resolve(data) {
            if (data instanceof myPromise) {
                return data;
            } else {
                return new myPromise((resolve) => {
                    resolve(data);
                })
            }
        }
        static reject(message) {
            return new myPromise((resolve, reject) => {
                reject(message);
            })
        }
        static all(pros) {
            return new myPromise((resolve, reject) => {
                const result = pros.map(p => {
                    const obj = {
                        result: undefined,
                        status: false
                    };
                    p.then(data => {
                        obj.result = data;
                        obj.status = true;
                        const unResolved = result.filter(r => !r.status);
                        if (unResolved.length === 0) {
                            resolve(result.map(r => r.result))
                        }
                    }, message => {
                        reject(message);
                    })
                    return obj;
                })
            })
        }
        static race(pros) {
            return new myPromise((resolve, reject) => {
                pros.forEach(p => {
                    p.then(data => {
                        resolve(data);
                    }, message => {
                        reject(message);
                    })
                })
            })
        }
    }
})(); //使用立即执行函数来避免命名重复

5. asyncawait

asyncawait是 [ES2016(ES7)] 新增的两个关键字。

  1. async用于简化Promise的创建,用于修饰函数,放在函数最开始的位置,被修饰函数的返回结果一定是Promise对象。
async function foo() {
    return 2;
}
//等效于
function foo() {
    return new Promise((resolve, reject) => {
        resolve(2);
    })
}
  1. await一定要放在async函数中。
async function foo2() {
    const result = await foo();
    console.log(result); //2
}
//等效于
function foo2() {
    return new Promise((resolve, reject) => {
        foo().then(data => {
            const result = data;
            console.log(result); //2
        })
    })
}
  1. 特殊情况(面试有几率考)
async function foo() {
    const result = await 1;
    console.log(result); //1
}
//等效于
function foo() {
    return new Promise((resolve, reject) => {
        Promise.resolve(1).then(data => {
            const result = data;
            console.log(result);
        })
        resolve();
    })
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容