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的基础,并且支持链式调用。
  2. handle方法是promise内部的方法。then方法传入的形参onFulfilled以及创建新Promise实例时传入的resolve均被push到当前promisecallbacks队列中,这是衔接当前promise和后邻promise的关键所在(这里一定要好好的分析下handle的作用)。
  3. 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>


image.png

总结


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


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


相关文章
|
29天前
|
数据采集 Web App开发 JavaScript
JavaScript爬虫进阶攻略:从网页采集到数据可视化
JavaScript爬虫进阶攻略:从网页采集到数据可视化
|
1月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
13 1
|
1月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
21 4
|
24天前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
24天前
|
前端开发 JavaScript
js开发:请解释Promise是什么,以及它如何解决回调地狱(callback hell)问题。
Promise是JavaScript解决异步操作回调地狱的工具,代表未来可能完成的值。传统的回调函数嵌套导致代码难以维护,而Promise通过链式调用`.then()`和`.catch()`使异步流程清晰扁平。每个异步操作封装为Promise,成功时`.then()`传递结果,出错时`.catch()`捕获异常。ES6的`async/await`进一步简化Promise的使用,使异步代码更接近同步风格。
12 1
|
2月前
|
前端开发 JavaScript API
JavaScript学习笔记(一)promise与async
JavaScript学习笔记(一)promise与async
|
2月前
|
前端开发 JavaScript UED
JavaScript中的异步编程和Promise
【2月更文挑战第3天】在Web开发中,JavaScript是一门非常重要的编程语言,而异步编程是JavaScript中的一个关键概念。本文将介绍JavaScript中的异步编程特点,以及如何使用Promise来更加优雅地处理异步操作,帮助开发者更好地理解和应用这一技术。
16 3
|
2月前
|
前端开发 JavaScript 数据处理
JavaScript中的异步编程及Promise对象
【2月更文挑战第3天】 传统的JavaScript编程模式在处理异步任务时常常会导致回调地狱和代码可读性较差的问题,而Promise对象的引入为解决这一问题提供了一种优雅的解决方案。本文将介绍JavaScript中的异步编程方式以及Promise对象的使用方法和优势,帮助读者更好地理解和运用异步编程技术。
19 8
|
2月前
|
前端开发 JavaScript
JavaScript中的异步编程与Promise
【2月更文挑战第1天】在Web开发中,JavaScript是一种常用的编程语言,而异步编程是其重要特点之一。本文将介绍JavaScript中的异步编程机制,探讨Promise对象的使用以及如何通过Promise处理异步操作,帮助读者更好地理解和应用JavaScript异步编程技术。
|
2月前
|
前端开发 JavaScript
JavaScript中的异步编程及Promise的应用
在前端开发中,异步编程是常见的需求,而Promise作为一种解决异步操作的技术,具有很高的应用价值。本文将介绍JavaScript中的异步编程原理,并结合实际案例详细讲解Promise的使用方法及其在前端开发中的应用。
11 2