promise原理及实现

回调地域:上一个回调函数中继续做事情,而且继续回调(在真实项目的Ajax请求中经常出现回调地域),异步请求,不方便维护

回调地域demo:先获取一个班级,拿到所有学生后获取学生的id,获取到学生的id后再继续操作一下东西

$.ajax({
    url: '/student',
    method: 'get',
    data: {
        class: 1
    },
    success: function (result) {
        $.ajax({
            url: '/sorce',
            method: 'get',
            data: {
                stuId: result
            },
            success: function (result) {
                let arr = result.map(item => item.id);

                $.ajax({
                    url: '/pass',
                    method: 'get',
                    data: {
                        stuId: arr
                    },
                    success: function (result) {
                        // ...继续做什么操作
                    }
                })
            }
        })
    }
})

Promise的诞生就是为了解决异步请求中的回调地域问题:它是一种设计模式,ES6中提供了一个JS内置类Promise,来实现这种设计模式

function ajax1 () {
    return new Promise(resolve => {
        $.ajax({
            url: '/student',
            method: 'get',
            data: {
                class: 1
            },
            // success: function (result) {
            //     resolve(result)
            // }
            success: resolve // success原本是一条函数,接收resolve函数
        })
    })
}

function ajax2 (arr) {
    return new Promise(resolve => {
        $.ajax({
            url: '/sorce',
            method: 'get',
            data: {
                stuId: arr
            },
            success: resolve
        })
    })
}

function ajax3 (arr) {
    return new Promise(resolve => {
        $.ajax({
            url: '/pass',
            method: 'get',
            data: {
                stuId: arr
            },
            success: resolve
        })
    })
}

// 更好的做法:Promise
ajax1.then(result => {
    return ajax2(result.map(item => item.id));
}).then(result => {
    return ajax3(result);
})

// 目前最好的做法async/await
async function handle() {
    // 此处的result就是三次异步请求后获取的信息
    let result = await ajax1();
    result = await ajax2(result.map(item => item.id));
    result = await ajax3(result);
}

Promise是用来管理异步编程的,它本身不是异步的

new Promise的时候会立即把executor函数执行(只不过我们一般会在executor函数中处理一个异步操作)

// 下面结果会先输出1再输出2,证明promise并不是异步的
let p1 = new Promise(() => {
    console.log(1)
});

console.log(2);

// 下面输出结果呢?如果答案不对需要去学习eventLoop(也叫事件环)
let p1 = new Promise(() => {
    setTimeout(_ => {
        console.log(1);
    }, 0);
    console.log(2)
});

console.log(3);

Promise状态和值

[[PromiseStatus]]
  • pending:初始状态,既不是成功,也不是失败状态
  • fulfilled(现在改为resolved):意味着操作成功完成
  • rejected:意味着操作失败
[[PromiseValue]]

Promise本身还有一个value值,用来记录成功的结果或失败的原因

一般在异步操作结束后,执行resolve/reject函数,执行这两个函数中的一个,都可以修改Promise的[[PromiseStatus]] / [[PromiseValue]]

let p1 = new Promise((resolve, reject) => {
    resolve('success');
});

console.log(3);
状态和值改变
let p1 = new Promise((resolve, reject) => {
    reject('fail');
}); // .catch(e => console.log(e))

console.log(3);
promise执行后会返回一个新的promise,所以catch后查看p1的值变为undefined,下面再讲

当状态改变后,无法再次扭转状态

let p1 = new Promise((resolve, reject) => {
    resolve('succcess');
    reject('fail');
});

console.log(3);
成功/失败的状态无法二次扭转

Promise.resolve和Promise.reject等价于一个Promise实例去调用executor中的resolve和reject

new Promise((resolve, reject) => {
    resolve(100);
    // reject(0);
})

Promise.resolve(100); // 等价于上面的写法
Promise.reject(0);

then

promise原型上有一个then方法,用于设置成功或者失败后处理的方法
Promise.prototype.then([resolvedFn], [rejectedFn])

let p1 = new Promise((resolve, reject) => {
    setTimeout(_ => {
        if(Math.random() < 0.5) {
            reject('Fail');
            return; // 这段代码不return也行,因为无法二次扭转状态
        }
        resolve('Success');
    }, 300);
});

p1.then(result => {
    console.log(result);
}, reason => {
    console.log(reason);
})

promise.then()结束会返回一个新的Promise实例(可以实现链式调用,then链)
[[PromiseStatus]]: 'pending'
[[PromiseValue]]: undefined

let p1 = new Promise((resolve, reject) => {
    resolve(100);
    // reject(0);
});

let p2 = p1.then(result => {
    console.log('success: ' + result);
    return result + 100;
}, reason => {
    console.log('fail: ' + reason);
    return -10;
});

let p3 = p2.then(result => {
    console.log('success: ' + result);
}, reason => {
    console.log('fail: ' + reason);
});
// success: 100, success: 200

总结then的执行结果如下:

  • p1这个new Promise出来的实例,成功或者失败,取决于executor函数执行的时候,执行的是resolve还是reject决定的,再或者executor函数执行发生异常错误,也是会把实例状态改为失败
  • p2 / p3这种每一次执行then方法返回的新实例的状态,有then中存储的方法执行的结果来决定最后的状态(上一个then中某个方法执行的结果,决定下一个then中哪一个方法会被执行)
    1. 不论是成功的方法执行,还是失败的方法执行(then中的两个方法),凡是执行抛出了异常,则都会把实例的状态改为失败
    2. 方法中如果返回一个新的Promise实例,返回这个实例的结果是成功还是失败,也决定了当前实例是成功还是失败
    3. 剩下的情况基本都是让实例变为成功的状态(方法返回的结果是当前实例的value值;上一个then中返回的结果会传递到下一个then的方法中)

then中也可以只写一个或者不写函数
单纯只有成功:Promise.then(fn)
单纯只有失败:Promise.then(null, fn)
当遇到一个then,要执行成功或者失败的方法,如果此方法并没有在当前then中被定义,则顺延到下一个对应的函数,如下demo:

Promise.reject(10).then(result => {
    console.log('success: ' + result);    
}).then(null, reason => {
    console.log('fail: ' + reason);
});
// fail: 10

也因为这个顺延的特点,所以一般项目中会直接使用catch来捕获错误,错误处理只会在其中必要的时候才会去额外处理。Promise.prototype.catch(fn)等价于Promise.prototype.then(null, fn)

Promise.reject(10).then(result => {
    console.log('success: ' + result);    
}).catch(reason => {
    console.log('fail: ' + reason);
});

all

Promise.all(arr):返回结果是一个Promise实例(all实例),要求arr数组中的每一项都是一个新的Promise实例,Promise.all是等待数组中的所有实例状态都为成功,才会让all实例状态为成功。value是一个集合,存储这arr中每一个实例返回的结果;凡是arr中有一个实例状态为失败,all实例的状态也是失败,返回的结果是按照arr中编写实例的顺序组合在一起的

let p1 = Promise.resolve(1);
let p2 = new Promise(resolve => {
    setTimeout(_ => {
        resolve(2);
    }, 1000);
})
let p3 = Promise.reject(3);

Promise.all([p1, p2, p3]).then(result => {
    console.log('success:' + result)
}).catch(reason => {
    console.log('fail: ' + reason); // fail: 3
})

Promise.all([p2, p1]).then(result => {
    console.log(result); // [2, 1]
    console.log('success:' + result); // success: 2,1
})

race

Promise.race(arr):和all不同的地方,race是竞赛,也就是arr中不管哪一个先处理完,处理完的结果作为race实例的结果(真实项目中基本用不到)

async/await

目前项目一般不会用到promise(除非是小程序项目),因为ES7提供了更方便的语法糖:async/await
async是让一个普通函数返回的结果变为[[PromiseStatus]]为[[PromiseValue]]为return结果的promise实例

async function fn () {
    return 10;
}

async function fn1 () {
    setTimeout(_ => {
        return 20;
    })
}

console.log(fn(), fn1());

return的返回值,并且是立即返回,因此延迟函数的返回值获取不到

不过一般项目中不会出现只写async,而是和await配套使用。await会等待当前promise的返回结果,只有结果返回的状态是resolved情况,才会吧返回结果复制给result
await不是同步编程,也是异步的(微任务,同then一样是微任务):当代码执行遇到await,构建一个异步的微任务(等待promise的返回结果,并且await下面的代码也都被列到任务队列中,而本身await是会被立即执行的),如果promise是失败状态,则await不会接收其返回结果,await下面的代码也不会被执行到

let p1 = Promise.resolve(100);
let p2 = new Promise(resolve => {
    setTimeout(_ => {
        resolve(20);
    }, 1000);
})
let p3 = Promise.reject(3);

async function fn () {
    console.log(1);
    let result = await p1;
    console.log(result);

    let result2 = await p2;
    console.log(result2);

    let result3 = await p3;
    console.log(result3);
    console.log(222);
}
fn();
console.log(2);
// 1, 2, 100, 20,可以看到222已经执行不到了,因为result3的promise没返回resolved,所以下面的代码都没法被执行

检验学习成果:请写出下面代码输出顺序

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2 () {
    console.log('async2');
}

console.log('script start');

setTimeout(() => {
    console.log('setTimeout');
}, 0);

async1();

new Promise(resolve => {
    console.log('promise1');
    resolve();
}).then(_ => {
    console.log('promise2');
})

console.log('script end');

/*
 * 输出结果
 * 'script start'
 * 'async1 start'
 * 'async2'
 * 'promise1'
 * 'script end'
 * 'async1 end'
 * 'promise2'
 * 'setTimeout'
 */

结合eventLoop,再来一道:

console.log(1);
setTimeout(_ => {console.log(2)}, 1000);
async function fn () {
    console.log(3);
    setTimeout(_ => { console.log(4) }, 20);
    return Promise.reject();
}
async function run () {
    console.log(5);
    await fn();
    console.log(6);
}
run();
for(let i = 0; i < 90000000; i++){} // 越150ms
setTimeout(_ => {
    console.log(7);
    new Promise(resolve => {
        console.log(8);
        resolve();
    }).then(_ => { console.log(9); });
}, 0);
console.log(10);

// 1 5 3 10 4 7 8 9 2

实现一个promise

promise的特点上面也都介绍了,估计实现的代码网上也是一搜一大把,我这里就直接给出实现代码了(不常用的racefinally不实现)

class PromiseModel {
    constructor (executor) {
        // 每一个Promise实例都有一个状态和结果属性
        this['[[promiseStatus]]'] = 'pending';
        this['[[promiseValue]]'] = undefined;

        // 用于存储基于then指定的成功或者失败的方法
        this.resolveArr = [];
        this.rejectArr = [];

        // 定义 resolve / reject 方法,用来改变Promise实例的状态和结果
        let change = (status, value) => {
            if(this['[[promiseStatus]]'] !== 'pending') return;

            this['[[promiseStatus]]'] = status;
            this['[[promiseValue]]'] = value;

            // 改变完状态后,把基于then指定的对应方法执行
            let fnArr = status === 'resolved' ? this.resolveArr : this.rejectArr;
            fnArr.forEach(item => {
                if (typeof item !== 'function') return;
                item(value);
            })
        }

        // 为了保证执行 resolve / reject 的时候,已经通过then把需要执行的方法弄好了,我们判断处理(没有方法的时候,我们让改变状态的操作延迟进行)
        let resolve = result => {
            if (this.resolveArr.length) {
                change('resolved', result);
                return;
            }

            let delayTimer = setTimeout(() => {
                change('resolved', result);
                clearTimeout(delayTimer);
            }, 0);
        }

        let reject = reason => {
            if (this.rejectArr.length) {
                change('rejected', reason);
                return;
            }

            let delayTimer = setTimeout(() => {
                change('rejected', reason);
                clearTimeout(delayTimer);
            }, 0);
        }

        // 异常捕获
        try {
            executor(resolve, reject);
        } catch (err) {
            reject();
            throw Error(err);
        }
    }

    then (resolveFn, rejectFn) {
        // 如果传递的参数不是函数(Null或者不传递),让成功或者失败顺延
        if (typeof resolveFn !== 'function') {
            resolveFn = result => {
                return result;
            };
        }

        if (typeof rejectFn !== 'function') {
            rejectFn = reason => {
                return PromiseModel.reject(reason);
            };
        }

        // 每一次执行then都会返回一个新的Promise实例(实现then的链式写法)
        return new PromiseModel((resolve, reject) => {
            // 只要执行新实例的 executor 函数中的 resolve / reject 就能知道新的实例是成功还是失败的
            this.resolveArr.push(result => {
                try {
                    // 不报错,则接受方法的返回结果,会根据结果判断成功还是失败
                    let x = resolveFn(result);
                    if (x instanceof PromiseModel) {
                        x.then(resolve, reject);
                        return;
                    }

                    resolve(x);
                } catch (err) {
                    // 方法执行报错,也代表新实例是失败的
                    reject(err.message);
                }
            });

            this.rejectArr.push(reason => {
                try {
                    // 不报错,则接受方法的返回结果,会根据结果判断成功还是失败
                    let x = rejectFn(reason);
                    if (x instanceof PromiseModel) {
                        x.then(resolve, reject);
                        return;
                    }

                    resolve(x);
                } catch (err) {
                    // 方法执行报错,也代表新实例是失败的
                    reject(err.message);
                }
            })
        })
    }

    // Promise.prototype.catch === Promise.prototype.then(null, fn)
    catch (rejectFn) {
        return this.then(null, rejectFn);
    }

    // 静态方法
    static resolve (result) {
        return new PromiseModel(resolve => {
            resolve(result);
        });
    }

    static reject (reason) {
        return new PromiseModel((_, reject) => {
            reject(reason);
        });
    }

    static all (arr) {
        return new PromiseModel((resolve, reject) => {
            let index = 0,
                results = [];
            
            for (let i = 0; i < arr.length; i++) {
                let item = arr[i];
                if(!(item instanceof PromiseModel)) continue;

                item.then(result => {
                    index++;
                    results[i] = result;

                    if (index === arr.length) resolve(results);
                }).catch(reason => {
                    // 只要有一个失败,整体就是失败
                    reject(reason);
                })
            }
        });
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352