web前端学习笔记之promise

本文详细介绍了Promise的概念、用途,通过抽奖案例、文件读取和AJAX请求展示了Promise的使用。讲解了Promise的状态、结果值属性、API以及关键问题,如状态转换、回调调用顺序、异常处理等。并提供了手写Promise类的实现,探讨了async函数和await表达式的运用,旨在帮助读者深入理解Promise在异步编程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 promise的理解和使用

1.1 promise是什么

  1. 抽象定义:
    1).es6中一门新的技术
    2).是JS中进行异步编程的新的解决方案,旧的方案是回调异步任务
  2. 具体含义
    1).promise是一个构造函数
    2).promise对象可以用来封装一个异步操作并且可以获取其成功/失败的结果
    异步编程有:fs文件操作、数据库操作、定时器、AJAX等

1.2 为什么要用promise

  1. 指定回调函数的方式更灵活,promise:启动异步任务=>返回promise对象,给这个对象绑定函数
  2. 支持链式调用,可以解决回调地狱问题,回调地狱就是一层回调函数里面还有一层回调函数,层数多了就是回调地狱,这样不便于异常处理

2 promise使用体验

2.1 抽奖案例

对两种不同的执行情况用两个函数返回,函数传参即为需要的数据,然后使用实例对象.then( ()=>{}, ()=>{} )来回调两个函数,函数的顺序就是在构造promise函数中形参的顺序。

function rand(m,n){
    return Math.ceil(Math.random()*(n-m+1))+m-1;
}
// const btn = document.querySelector('#btn');
// btn.addEventListener('click', function(){
//     setTimeout( () => {
//         let n = rand(1,100);
//         console.log(n);
//         if(n <= 30){
//             alert('恭喜你中了10万rmb大奖!!!');
//         }
//         else{
//             alert('再接再厉');
//         }
//     },1000);
// })

// promise改写这段代码
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        let n = rand(1,100);
        console.log(n);
        if(n <= 30){
            resolve(n);
        }
        else{
            reject(n);
        }
    },1000)
});
p.then((result)=>{
    alert('恭喜你中了10万rmb大奖!!!  '+result);
},(error)=>{
    alert('再接再厉 '+error);
})

2.2 fs读文件案例

const fs = require('fs');

// fs.readFile('./text.txt', 'utf-8',(err,data) => {
//     if(err)
//         console.log('读取文件失败');
//     else{
//         console.log(data);
//     }
// })

let p = new Promise( (resolve, reject) => {
    fs.readFile('./text.txt', 'utf-8',(err,data) => {
        if(err)
            reject(err);
        else{
            resolve(data);
        }
    });
});
p.then((data) => {
    console.log(data);
} ,(err) => {
    console.log('读取文件失败');
});

2.3 AJAX请求

var btn = document.querySelector('button');
btn.addEventListener('click',function(){
    const p = new Promise((resolve, reject) =>{
        const xhr = new XMLHttpRequest();
        xhr.open('GET','http://www.baidu.com');
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status >= 200&& xhr.status<300){
                    resolve(xhr.response);
                }else{
                    reject(xhr.status);
                }
            }
        }
    });
    p.then(value => {
        console,log(value);
    }, reason =>{
        console.warn(reason);
    })
})

2.4 函数封装promise读文件fs

这样封装以后,每次读文件只需要调用函数然后写对应的回调函数就行。

function mineReadFile(path){
    return new Promise((resolve, reject)=>{
        require('fs').readFile(path,'utf-8',(err,data)=>{
            if(err) reject(err);
            resolve(data);
        })
    })
}

mineReadFile('./成绩-ok.txt').then((value)=>{
    console.log(value);
},(reason)=>{
    console.log(reason);
});

2.5 util.promise方法进行promise风格转化

node.js中的util有一个promisify方法,引入util模块后就可以使用。
util.promisify(original):官方定义——传入一个遵循常见的错误优先的回调风格的函数,即以(err, value)=>...回调,并返回一个promise版本。
这个方法的原理应该就是 2.4 的封装,不过要把内部函数作为参数传进去。

const util = require('util');
const fs = require('fs');
let mineReadFile  = util.promisify(fs.readFile);
mineReadFile('./1.txt').then( ()=>{ }, ()=>{ } );

2.6 封装AJAX请求

Ajax请求要导入jQuery

function sendAJAX(url){
    return new Promise((resolve, reject)=>{
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status >= 200 && xhr.status < 300){
                    resolve(xhr);
                }
                else{
                    reject(xhr.status);
                }
            }
        }
    })
}
sendAJAX('www.baidu.com').then(value => {
    console.log(value);
}, warn => {
    console.log(warn);
})

3 promise对象状态属性

打印promise对象实例,可以看到有一个属性叫PromiseState,意思是这个实例对象的状态
实例状态(PromiseState)有三种:
- pending:未决定的
- resolve等同于fulfilled:成功
- rejected:失败
状态只能由pending到另外两种的转换一旦成功和失败,就不会再改变,所以成功和失败是不可能相互转换的

4 promise对象结果值属性

promise对象实例还有个属性叫结果值PromiseResult,存放的是对象成功或失败的结果,其实就是reject和resolve函数的传参,我们把结果作为参数传递了。

5 promise的API

  1. Promise构造函数:Promise (executor) { }
    (1) executor函数:执行器,(resolve, reject) => { }
    (2) resolve函数:内部定义成功时我们调用的函数 value => { }
    (3) reject函数:内部定义失败时我们调用的函数 reason => { }
    executor 会在Promise内部立即同步调用,异步操作会在执行器中执行
  2. Promise.prototype.then方法,指定成功/失败的回调函数,定义在原型上,(onResolve, onReject) => {} :
    (1) onResolve函数,成功的回调函数 value => { }
    (2) onReject函数,失败的回调函数 reason => { }
    (3) 返回一个新的promise对象
  3. Promise.prototype.catch方法,只能指定失败的回调,(onReject) => {}
  4. Promise.resolve方法,(value) => {}
    (1) value:成功的数据或者promise对象
    (2) 返回一个成功的promise对象
  5. Promise.reject方法,(reason) => {}
    (1) reason失败的原因
    (2) 返回一个失败的对象
  6. Promise.all方法,(promises) => {}
    (1) promises:包含n个promise的数组
    (2) 说明:返回一个新的promise,只有promises都成功才成功,返回所有的成功结果;只要有一个失败就直接失败,所以多个失败也只返回第一个失败的结果
  7. Promise.race方法,(promises) => {}
    (1) promises:包含n个promise的数组
    (2) 返回结果由第一个执行完毕产生结果的promise决定,相当于多个promise竞速

6 Promise的几个关键问题

6.1 如何改变Promise的状态?

  1. resolve函数,由pending => fulfilled(resolved)
  2. reject函数,由pending => rejected
  3. throw抛出错误
let p = new Promise((resolve, reject) => {
// 只能三选一
    // 1. resolve函数,由pending => fulfilled(resolved)
    resolve('ok');
    // 2. reject函数,由pending => rejected
    reject('error');
    // 3. 抛出错误
    throw '出问题了';
})

6.2 一个Promise指定多个成功/失败回调函数,都会调用吗?

都会

6.3 改变Promise状态和指定回调函数谁先谁后?

都有可能,看具体指定回调和改变状态的执行情况;一般来说如果promise内部改变状态的函数是同步执行,那就是先改变状态,但是如果是个异步函数,比如说定时器,当指定回调已经完成时定时器还没有执行,那就是指定回调先了。

6.4 promise.then()返回的新promise的结果状态由什么决定?

  1. 简单来说:由then()指定的回调函数执行的结果决定
  2. 详细解释:
    (1) 如果是抛出异常throw,新promise变为rejected,reason()抛出异常
    (2) 如果返回的是非promise的任意值,新promise变为resolved,value为返回值(不论是resolve还是reject的返回)
    (3) 如果返回的是一个新的promise,此promise的结果就会成为新promise的结果

6.5 promise如何串联多个操作任务

  1. promise的then()返回一个新的promise,可以展开成then的链式调用

  2. 通过then的链式调用串联多个同步/异步任务

    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('ok');
        },2000)
    })
    p.then(value =>{
        // 第一个then
        return new Promise((resolve, reject) => {
            resolve(value)
        })
    }, reason => {}).then(value => {
        // 第二个then
        console.log(value);
    }).then( value => {
        // 第三个then
        console.log(value);
    })
    

    输出:ok undefined
    分析:计时器时间一到,会改变p的状态为成功,p在这之前通过then(第一个)指定了成功的回调函数;这个回调函数的返回值是一个新的promise,且新promise的状态也通过resolve指定为成功,通过新函数的then(第二个)指定了它的回调函数为打印ok;新函数的then(第二个)也会返回一个新的promise,但是这里then(第二个)的返回值并不是回调函数,什么也没有返回也就是undefined,所以新新promise状态为成功,then(第三个)执行时没有任何传参,value为undefined

6.6 promise异常穿透?

  1. 当使用promise链式调用时,可以在最后指定失败的回调
  2. 前面任何操作出现了异常,都会传到最后失败的回调中
  3. 理解:某一层失败,那么就会执行失败的回调,抛出异常,这样新的promise就也是失败,新的promise失败,新新promise也失败,一层层抛出,直至最后的失败回调,中间层的promise没有写失败回调,不做任何处理。

6.7 promise链怎么中断?

  1. 当使用then的链式调用时,在中间中断,就不会再调用其后的回调函数
  2. 方法:让某一个回调函数返回pendding状态的promise对象(有且只有这一种方法)

7 手写promise

7.1 带注释版

// 用类封装Promise
class Promise {
    // Promise构造方法,接收一个函数作为参数,
    // 这个函数就是使用时用来改变Promise对象状态的函数,所以这个函数传入Promise后直接调用
    // exeutor传入了两个函数参数,就是resolve和reject, 在同步执行时会按照情况调用
    constructor(exeutor) {
        this.promiseState = 'pendding';
        this.promiseResult = null;
        // callback用来保存回调
        // 异步任务时,会先执行then,结果为pendding,
        // 等到异步执行时,改变了状态,但是then已经执行过了,那么对应的回调函数(使用者自己设置的)就不能再被调用了,
        // 而且exeutor并不知道使用者设置的回调函数内容
        // 那么此时,用callback来保存这两个函数的内容,在异步执行时,就可以通过callback来调用这两个回调函数
        // 在resolve和rejected两个改变状态的函数被调用时,判断callback有无内容,
        // 有内容就说明then先执行了,为异步任务,使用callback调用
        // 有多个回调,用数组来存储,每个位置存一个对象,每个对象里存两个回调函数
        this.callback = [];
        const that = this;
        function resolve(data) {
            // resolve函数的任务
            // 1. 修改对象的状态(promiseState)
            // 2. 设置对象结果值(promiseResult)

            // 状态只能改变一次
            if (that.promiseState !== 'pendding') return;

            // 这两个值都是属于实例对象的,打印实例对象可以看到,所以用this来指向
            // 注意:resolve函数不是实例对象调用的,而是window
            // 所以要提前存好this,在这里才能正确指向实例对象的属性;
            // 或者使用箭头函数(箭头函数没有this,所以如果用到this就是定义它的对象) let resolve = (data) => {}
            that.promiseState = 'fulfilled';
            that.promiseResult = data;

            // 当改变状态的函数是异步任务时,会先执行then,再改变状态,那么这个时候回调函数就是在改变状态后执行
            // 这个时候需要拿到回调函数,用callback来保存这几个回调函数,然后在这里调用callback,
            // 否则就算状态改变了回调函数也不会再被执行了
            if (that.callback.onResolve) {
                // 要注意回调函数都是异步执行的哦
                setTimeout(() => {
                    that.callback.forEach(item => {
                        item.onResolve;
                    })
                });
            }
        }
        function reject(data) {
            // 状态只能改变一次
            if (that.promiseState !== 'pendding') return;

            that.promiseState = 'rejected';
            that.promiseResult = data;
            
            if (that.callback.onReject) {
                setTimeout(() => {
                    that.callback.forEach(item => {
                        item.onReject;
                    })
                })
            }
        }
        // 注意:抛出异常也是可以改变对象状态的
        try {
            exeutor(resolve, reject)(); // 同步执行传入的参数
        }
        catch (e) {
            reject(e);
        }
    }
    // 添加then方法,传入了两个函数作为参数
    then(onResolve, onReject) {
        // 异常穿透处理
        // 如果函数没有写失败回调函数,当函数失败时,就会直接报错,
        // 所以我们让没传入的失败回调默认为继续向下抛出错误就行,这样下一个promise也因为throw状态为失败
        if (typeof onReject !== 'function') {
            onReject = reason => {
                throw reason;
            }
        }
        // 基于异常穿透,扩展一下,让成功回调函数没传入时也有一个默认内容,增强容错性
        // 默认为返回成功值。
        if (typeof onResolve !== 'function') {
            onResolve = value => value; //简写,return value
        }
        // then方法返回一个新的promise,新promise的状态由then执行的结果的返回值决定
        // 用return返回一个Promise对象
        return new Promise((resolve, reject) => {
            const self = this;
            // 把这个根据执行结果返回新Promise的函数封装起来,在then的具体指定回调时直接调用,
            // 同步、异步都要用,反正只要执行回调函数的地方都要用到
            // 让指定回调和返回同时执行
            // 这里用箭头函数的话内部用this是没有问题的
            let callback = (type) => {
                // 抛出异常的话,新Promise就是失败状态
                try {
                    // 用result拿到当前promise的执行结果,不管当前promise的状态是什么,
                    // 新promise只看它执行后的结果,最主要就是关注它的回调函数是不是又是一个promise

                    // 这句话就是既执行回调又将结果保存起来
                    let result = type(self.promiseResult);
                    if (result instanceof Promise) {
                        // 如果使用者嵌套了Promise,那么result就是这个Promise, 那么新Promise的状态由这个promise的状态来决定
                        // 使用它的then方法指定新函数的状态,
                        // 注意,一般来说then都是指定自己的回调函数,但是这里传进来的使用者Promise相当于返回给了新Promise,所以只要改变状态,也只能改变状态,
                        // 使用者传进来的Promise的then不需要在这里改,改新函数就行了,否则会嵌套太多回调
                        result.then(v => { resolve(v) }, r => { reject(r) })
                    }
                    else {
                        // 如果返回的不是Promise,那么它的状态一定是成功,调用对应函数改变状态。
                        resolve(result);
                    }
                } catch (e) {
                    // 抛出异常
                    reject(e);
                }
            }
            // 状态为成功时调用onResolve,失败时调用onReject
            // 设置回调函数是异步的(then异步),用setTimeout设置异步
            if (this.promiseState === 'fulfilled') {
                setTimeout(() => {
                    callback(onResolve);
                })
            }
            if (this.promiseState === 'rejected') {
                setTimeout(() => {
                    callback(onReject);
                })
            }
            if (this.promiseState === 'pendding') {
                // callback是一个数组,then回调函数可以设置多个,都可以执行
                this.callback.push({
                    // 异步要返回执行结果也可以调用callback,只要把它用function包起来,回调函数就可以正常被调用了
                    onResolve: function () {
                        callback(onResolve);
                    },
                    onReject: function () {
                        callback(onReject);
                    }
                })
            }
        })
    }

    // catch方法添加,传入一个函数参数作为失败回调,只指定失败回调
    // 前面已经写好了then,其实就是只给promise指定失败回调,resolve设置为undefined就行
    // 另外还要实现异常穿透,需要在then的抛出异常中设置允许reject没有定义的情况,这种情况下就设置reject为继续抛出异常
    // 事实上,没传onResolve的时候也应该考虑,有些情况就是不需要
    // 这样写完以后,这一层Promise没传回调函数,它的下一层Promise仍然能根据它的构造函数的改变状态情况得到自己的状态
    catch(onReject) {
        return taht.then(undefined, onReject);
    }

    // resolve方法,它是属于Promise,不是属于实例对象的,静态函数
    // 这个方法的目的是快速获得一个成功的对象
    static resolve = (value) => {
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then((v) => {
                    resolve(v);
                }, (r) => {
                    reject(r);
                })
            }
            else {
                resolve(value);
            }
        })
    }

    // reject方法封装,不管Promise状态如何改变,属于Promise,静态函数
    // 快速获得一个失败的对象
    static reject(reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        })
    }

    // all方法封装
    //传入多个Promise对象,都成功新promise才成功,
    // 如果成功,就把这个回调函数先存起来,要不然新Promise就直接成功了,后面要是失败了就不能改变它的状态了
    // 如果失败,直接执行
    static all(promises) {
        return new Promise((resolve, reject) => {
            let count = 0;
            let arr = [];
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    count++;
                    arr[i] = v;
                    if (count === promises.length) {
                        resolve(arr);
                    }
                }, r => {
                    reject(r)
                })
            }
        })
    }
    // race方法封装
    // 由最先执行完改变状态的promise决定,直接让每一个promise的then都指定新Promise状态就行了
    static race(promises) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    resolve(v);
                }, r => {
                    reject(r);
                })
            }
        })
    }
}

7.2 去掉注释版

// 用类封装Promise
class Promise {
    constructor(exeutor) {
        this.promiseState = 'pendding';
        this.promiseResult = null;
        this.callback = [];
        const that = this;
        function resolve(data) {
            if (that.promiseState !== 'pendding') return;
            that.promiseState = 'fulfilled';
            that.promiseResult = data;
            if (that.callback.onResolve) {
                setTimeout(() => {
                    that.callback.forEach(item => {
                        item.onResolve;
                    })
                });
            }
        }
        function reject(data) {
            if (that.promiseState !== 'pendding') return;
    
            that.promiseState = 'rejected';
            that.promiseResult = data;
                
            if (that.callback.onReject) {
                setTimeout(() => {
                    that.callback.forEach(item => {
                        item.onReject;
                    })
                })
            }
        }
        try {
            exeutor(resolve, reject)();
        }
        catch (e) {
            reject(e);
        }
    }
    then(onResolve, onReject) {
        if (typeof onReject !== 'function') {
            onReject = reason => {
                throw reason;
            }
        }
        if (typeof onResolve !== 'function') {
            onResolve = value => value; //简写,return value
        }
        return new Promise((resolve, reject) => {
            const self = this;
            let callback = (type) => {
                try {
                    let result = type(self.promiseResult);
                    if (result instanceof Promise) {
                        result.then(v => { resolve(v) }, r => { reject(r) })
                    }
                    else {
                        resolve(result);
                    }
                } catch (e) {
                    reject(e);
                }
            }
            if (this.promiseState === 'fulfilled') {
                setTimeout(() => {
                    callback(onResolve);
                })
            }
            if (this.promiseState === 'rejected') {
                setTimeout(() => {
                    callback(onReject);
                })
            }
            if (this.promiseState === 'pendding') {
                this.callback.push({
                    onResolve: function () {
                        callback(onResolve);
                    },
                    onReject: function () {
                        callback(onReject);
                    }
                })
            }
        })
    }
    catch(onReject) {
        return taht.then(undefined, onReject);
    }
    static resolve = (value) => {
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then((v) => {
                    resolve(v);
                }, (r) => {
                    reject(r);
                })
            }
            else {
                resolve(value);
            }
        })
    }
    static reject(reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        })
    }
    static all(promises) {
        return new Promise((resolve, reject) => {
            let count = 0;
            let arr = [];
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    count++;
                    arr[i] = v;
                    if (count === promises.length) {
                        resolve(arr);
                    }
                }, r => {
                    reject(r)
                })
            }
        })
    }
    static race(promises) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    resolve(v);
                }, r => {
                    reject(r);
                })
            }
        })
    }
}

8 async函数

  1. 返回值为promise对象
  2. 返回结果由async函数执行返回值决定,和then的原理一模一样
async function main(){
	return 111;
}
let result = main();  // result.promiseState = 'fulfilled'

9 await表达式

  1. await右侧的表达式一般为promise对象,但也可以是其他的值
  2. 如果await右侧是promise对象,那么await返回的是promise成功的值
  3. 如果表达式是其他值,直接将此值作为await的返回值
  4. 注意:await只能写在async函数中;如果await的promise失败了,就会抛出异常,需要try…catch捕获处理
async function main(){
    let p = new Promise((resolve,reject) => { 
        resolve('ok');
    })
    let q = new Promise((resolve,reject) => { 
        reject('no');
    })
    // 1.右侧为promise对象
    let res1 = await p;
    console.log(res1);
    // 2.promise是失败的
    try {
        let res2 = await q;
    } catch (e) {
        console.log(e)
    }
}
main();

10 async与await结合实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值