JavaScript——Promise进阶

简介: JavaScript——Promise进阶

为什么出现Promise


在javascript开发过程中,代码是单线程执行的,同步操作,彼此之间不会等待,这可以说是它的优势,但是也有它的弊端,如一些网络操作,浏览器事件,文件等操作等,都必须异步执行,针对这些情况,起初的操作都是使用回调函数实现。


实现方式如下(伪代码):


function One(callback) {
    if (success) {
        callback(err, result);
    } else {
        callback(err, null);
    }
}
One(function (err, result) {
    //执行完One函数内的内容,成功的结果回调回来向下执行
})


上述代码只是一层级回调,如果代码复杂后,会出现多层级的回调,代码可读性也会很差,那有没有一种方式,不用考虑里面的内容,直接根据结果成功还是失败执行下面的代码呢?有的,Promise(承诺),在ES6中对Promise进行了统一的规范


什么是promise


Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。


Promise规范如下:


  • 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
  • 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
  • promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
  • then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。


Promise原理与讲解


原理剖析--极简promise


由以上规范就容易就能实现这个类的大致结构


<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
<script type="text/javascript">
    class Promise {
        callbacks = [];
        constructor(fn) {
            fn(this.resolve.bind(this));
        }
        then(onFulfilled) {
            this.callbacks.push(onFulfilled);
        }
        resolve(value) {
            this.callbacks.forEach(fn => fn(value));
        }
    }
    //Promise应用
    let p = new Promise(resolve => {
        setTimeout(() => {
            resolve('测试');
        }, 2000);
    })
    p.then((tip)=>{
        console.log('tip1',tip) // tip1,测试
    })
    p.then((tip)=>{
        console.log('tip2',tip) // tip2,测试
    })
</script>
</body>
</html>


这个简单版本大致逻辑是:


实例化Promise时,其类构造函数初始化执行了回调函数,并将绑定了当前实例的resolve方法作为参数回传给回到函数。接着调动Promise对象的then方法, 注册异步操作完成后的回调函数。 当异步操作完成时,调用resolve方法, 该方法执行then方法注册的回调函数 这里then 方法注册完成时的回到是一个数组, then方法可以多次调用。注册的函数会在异步操作完成后根据添加的顺序依次执行。


相信仔细的人应该可以看出来,then方法应该能够链式调用,但是上面的最基础简单的版本显然无法支持链式调用。想让then方法支持链式调用,其实也是很简单的(如果写过jQuery插件的同学应该熟悉):


// 在上方代码添加
then(onFulfilled) {
    this.callbacks.push(onFulfilled);
    return this; //看这里
}
// 修改其调用方式
p.then((tip)=>{
    console.log('tip1',tip) // tip1,测试
}).then((tip)=>{
    console.log('tip2',tip) // tip2,测试
})


加入延时机制


首先我们吧上方代码的栗子中 setTimeout 去掉,在执行一下


//Promise应用
let p = new Promise(resolve => {
    console.log('同步执行1');
    resolve('同步执行2');
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});


发现只打印了 同步执行1


image.png


这显然是不允许的,Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve执行之前,then方法已经注册完所有的回调。我们可以这样改造下resolve函数


// 修改上方代码
resolve(value) {
    //看这里
    setTimeout(() => {
        this.callbacks.forEach(fn => fn(value));
    });
}


//通过`setTimeout`机制,将`resolve`中执行回调的逻辑放置到`JS`任务队列末尾,以保证在`resolve`执行时,`then`方法的回调函数已经注册完成.


image.png

加入状态


为了解决上面提到的问题, 我们需要加入状态机制, 也就是大家熟知的pending, fulfilled, rejected。


Promises/A+ 规范中明确规定了, pending 可以转化为fulfilled或rejected并且只能转化一次。 也就是说如果pending转为fulfiled就不能再转化到rejected。 并且fulfilled 和 rejected状态只能pending转化而来, 两者之间不能互相转换。


image.png


// 修改如下
class Promise {
    callbacks = [];
    state = 'pending';//增加状态
    value = null;//保存结果
    constructor(fn) {
        fn(this.resolve.bind(this));
    }
    then(onFulfilled) {
        if (this.state === 'pending') {
            //在resolve之前,跟之前逻辑一样,添加到callbacks中
            this.callbacks.push(onFulfilled);
        } else {
            //在resolve之后,直接执行回调,返回结果了
            onFulfilled(this.value);
        }
        return this;
    }
    resolve(value) {
        this.state = 'fulfilled';//改变状态
        this.value = value;//保存结果
        this.callbacks.forEach(fn => fn(value));
    }
}


resolve 执行时, 会将状态设置为 fulfilled , 并把 value 的值存起来, 在此之后调用 then 添加的新回调都会立即执行, 直接返回保存的value值


有同学发现增加了状态的后代码把setTimeout去掉了,原因是:


resolve执行时,会将状态设置为fulfilled,在此之后调用then添加的新回调,都会立即执行


链式Promise


那么这里问题又来了,如果用户再then函数里面注册的仍然是一个Promise,该如何解决?比如下面


p()
.then(()=>{
    // 这里返回Promise
    return new Promise(function (resolve) {
         resolve(resolve);
    });
})
.then(function (res) {
    // res拿到上一个Promise.resolve的值
});


真正的链式调用


真正的链式Promise是指在当前Promise达到fulfilled状态后, 即开始进行下一个Promise(后邻Promise)。 那么我们如何衔接当前Promise和后邻Promise呢,这个是重点,修改较多,我附一段完整的代码


<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
<script type="text/javascript">
    class Promise {
        callbacks = [];
        state = 'pending';//增加状态
        value = null;//保存结果
        constructor(fn) {
            fn(this.resolve.bind(this));
        }
        then(onFulfilled) {
            return new Promise(resolve => {
                this.handle({
                    onFulfilled: onFulfilled || null,
                    resolve: resolve
                });
            });
        }
        handle(callback) {
            if (this.state === 'pending') {
                this.callbacks.push(callback);
                return false;
            }
            //如果then中没有传递任何东西
            if (!callback.onFulfilled) {
                callback.resolve(this.value);
                return false;
            }
            var ret = callback.onFulfilled(this.value);
            callback.resolve(ret);
        }
        resolve(value) {
            if (value && (typeof value === 'object' || typeof value === 'function')) {
                var then = value.then;
                if (typeof then === 'function') {
                    then.call(value, this.resolve.bind(this));
                    return;
                }
            }
            this.state = 'fulfilled';//改变状态
            this.value = value;//保存结果
            this.callbacks.forEach(fn => fn(value));
        }
    }
    // then中返回Promise
    let p = new Promise(resolve => {
        console.log('同步执行1');
        resolve('同步执行2');
    }).then(tip => {
        console.log(tip);
        return new Promise((resolve)=>{
            resolve('同步执行3')
        })
    }).then(tip => {
        console.log(tip);
    });
</script>
</body>
</html>


image.png

  1. then方法中,创建并返回了新的Promise实例,这是串行Promise的基础,并且支持链式调用。


  1. handle方法是promise内部的方法。then方法传入的形参onFulfilled以及创建新Promise实例时传入的resolve均被push到当前promisecallbacks队列中,这是衔接当前promise和后邻promise的关键所在(这里一定要好好的分析下handle的作用)。


  1. resolve 方法中会先检查value是不是 Promise 对象, 如果是一个新的Promise, 那么先不改变当前 promise 的状态。


错误处理


在异常操作失败时,标记其状态为rejected, 并执行注册的失败回调


<!--完整代码-->
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
<script type="text/javascript">
    class Promise {
        callbacks = [];
        state = 'pending';//增加状态
        value = null;//保存结果
        constructor(fn) {
            fn(this.resolve.bind(this), this.reject.bind(this));
        }
        then(onFulfilled, onRejected) {
            return new Promise((resolve, reject) => {
                this.handle({
                    onFulfilled: onFulfilled || null,
                    onRejected: onRejected || null,
                    resolve: resolve,
                    reject: reject
                });
            });
        }
        handle(callback) {
            if (this.state === 'pending') {
                this.callbacks.push(callback);
                return;
            }
            let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
            if (!cb) {//如果then中没有传递任何东西
                cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
                cb(this.value);
                return;
            }
            // 这里处理,如果在执行成功回调、失败回调时代码出错怎么办,对于类似异常, 处理也很简单, 可以使用try-catch捕获错误, 然后将相应的promise状态设置为rejected状态
            let ret;
            try {
                ret = cb(this.value);
                cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
            } catch (error) {
                ret = error;
                cb = callback.reject
            } finally {
                cb(ret);
            }
        }
        resolve(value) {
            if (value && (typeof value === 'object' || typeof value === 'function')) {
                var then = value.then;
                if (typeof then === 'function') {
                    then.call(value, this.resolve.bind(this), this.reject.bind(this));
                    return;
                }
            }
            this.state = 'fulfilled';//改变状态
            this.value = value;//保存结果
            this.callbacks.forEach(callback => this.handle(callback));
        }
        reject(error) {
            this.state = 'rejected';
            this.value = error;
            this.callbacks.forEach(callback => this.handle(callback));
        }
    }
    //Promise应用
    let p = new Promise(resolve => {
        console.log('同步执行1');
        resolve('同步执行2');
    }).then(tip => {
        return new Promise((resolve,reject)=>{
            // 做个随机数控制resolve或者reject的调用
            if(parseInt(Math.random()*10) > 4){
                resolve(tip+' 成功')
            }else{
                reject(tip+' 失败')
            }
        })
    }).then(result => {
        console.log(result);
    }, error => {
        console.log(error);
    });
</script>
</body>
</html>


总结



promise 里面的 then 函数仅仅是注册了后续需要执行的回调函数,同时返回一个新的Promise对象,以延续链式调用,真正的逻辑是在handle里面


对于内部 pending 、fulfilled 和 rejected 的状态转变,通过 handler 触发 resolve 和 reject 方法,然后更改state状态值


相关文章
|
7月前
|
前端开发 JavaScript
从0开始学习JavaScript--JavaScript使用Promise
JavaScript中的异步编程一直是开发中的重要话题。传统的回调函数带来了回调地狱和代码可读性的问题。为了解决这些问题,ES6引入了Promise,一种更现代、更灵活的异步编程解决方案。本文将深入探讨JavaScript中如何使用Promise,通过丰富的示例代码演示Promise的基本概念、链式调用、错误处理等方面的用法,帮助大家更全面地理解和应用Promise。
|
6月前
|
前端开发 JavaScript 开发者
JavaScript进阶-Promise与异步编程
【6月更文挑战第20天】JavaScript的Promise简化了异步操作,从ES6开始成为标准。Promise有三种状态:pending、fulfilled和rejected。基本用法涉及构造函数和`.then`处理结果,如: ```javascript new Promise((resolve, reject) =&gt; { setTimeout(resolve, 2000, &#39;成功&#39;); }).then(console.log); // 输出: 成功
91 4
|
3月前
|
前端开发 JavaScript
JavaScript Promise-2
JavaScript Promise-2
30 3
|
3月前
|
Web App开发 前端开发 JavaScript
JavaScript Promise-1
JavaScript Promise
28 3
|
7月前
|
JSON 前端开发 JavaScript
【JavaScript技术专栏】JavaScript异步编程:Promise、async/await解析
【4月更文挑战第30天】JavaScript中的异步编程通过Promise和async/await来解决回调地狱问题。Promise代表可能完成或拒绝的异步操作,有pending、fulfilled和rejected三种状态。它支持链式调用和Promise.all()、Promise.race()等方法。async/await是ES8引入的语法糖,允许异步代码以同步风格编写,提高可读性和可维护性。两者结合使用能更高效地处理非阻塞操作。
102 0
|
4月前
|
前端开发 JavaScript API
JavaScript 中的 Promise
Promise 是 JavaScript 中用于处理异步操作的一种模式,它提供了一种比传统的回调函数更清晰、更易于管理和控制的方式来处理异步流程。
39 4
|
7月前
|
前端开发 JavaScript
JavaScript Promise
JavaScript Promise 是异步编程的一种解决方案,它表示一个尚未完成但预期在未来完成的操作的结果。Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。通过 Promise,我们可以将回调函数的嵌套改为链式调用,从而提高代码的可读性和可维护性。
36 1
|
7月前
|
前端开发 JavaScript
JavaScript基础知识:什么是 Promise?如何使用它处理异步操作?
JavaScript基础知识:什么是 Promise?如何使用它处理异步操作?
58 1
|
7月前
|
前端开发 JavaScript
JavaScript学习 -- Promise的使用
JavaScript学习 -- Promise的使用
49 0
|
存储 移动开发 前端开发
《现代Javascript高级教程》JavaScript中的异步编程与Promise
JS中的异步编程与Promise 一、JavaScript的异步编步机制 在了解JavaScript的异步机制之前,我们首先需要理解JavaScript是一种单线程语言。单线程就意味着所有的任务需要按照顺序一次执行,如果前一个任务没有完成,后一个任务就无法开始。这个特性在执行大量或耗时任务时可能会导致阻塞或者界面卡死,这显然是不可取的。
93 1