Promise 最完整介绍与实现解密

JavaScript Promise 快速入门 这一篇对Promise 及其基本用法做了简单的介绍。本篇介绍Promise 的完整功能以及对Promise 的来源以及实现进行解密。

Promise 是什么?

该怎么解释Promise 呢? 试着从以下几种角度来理解:

  • 从代码的角度: Promise 是ECMAScript 6中提供了一个类 ,Promise 对象可以让异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数的方式。
  • 从Promise工作机制的角度:Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是then方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。

如果到这里还不理解Promise 是什么?那就看看Promise 是怎么来的。

Promise 是怎么来的?

JavaScript是单线程语言,也就是没有像Java语言一样有Thread这种线程类(可以单独的启动一个线程运行任务)。 换言之, JavaScript是一种同步的编程语言, 但是往往JavaScript也有很多异步编程的需要,最典型的就是AJAX,如何实现AJAX呼叫后端服务之后的回调呢? 这里为了让演示更简单和直观,使用setTimeout延迟调用代替AJAX。

传统的回调方式

传统的回调将回调函数作为参数传递给异步函数,在异步函数执行完成之后,调用回调函数。以以下代码为例:

var successCallBack = function(result){
    console.log("成功执行的结果是="+result)
}
var failCallBack = function(result){
    console.log("失败执行的结果是="+result)
}
function asyncFunc(successCallBackFunc,failCallBackFunc){
    var isSuccess = true;
      //使用setTimeout模拟异步行为
      setTimeout(function(){
        //根据返回结果设置isSuccess的值
         if(isSuccess){
           successCallBackFunc("返回值1");
         }else{
           failCallBackFunc("返回值2");
         }
     },1000);
}

上面的这种写法对于单层的回调来说看起来也能接受,但是如果是多层回调的话,代码就很复制了,也就是经典的回调地狱。

Promise 的出现

早期的JavaScript一般也是使用上面的方式来处理回调的,是否有避免回调地狱的写法呢?
于是有一些JavaScript组织探求比较好的写法, 这其中最知名的就是CommonJS 。

CommonJS 是一个由服务的JavaScript运行库作者组成的小组,也就是CommonJS 是JavaScript的一个社区, 或者是一个民间组织,不是官方的标准。

CommonJS 提出了Promise 的方式,JavaScript也很认同这种方式,于是在JavaScript发布的新版本ECMAScript 6中,就将Promise 作为JavaScript的一个原生类了。

ECMAScript 6 是2015年发布的JavaScript 语言的标准。Promise 是ECMAScript 6 中的一个类。

Promise的基本用法

对于上面的立子, 使用Promise,aysnFunc返回一个Promise 对象,则简写为:

let promise = aysnFunc();
promise.then(successCallBackFunc,failCallBackFunc);

再简化一点:
aysnFunc().then(successCallBackFunc,failCallBackFunc);

为什么叫Promise ?

Promise ,翻译是承诺, 也就是保证某人会做某事或是某件事情会发生。结合then() 方法等,形象的理解一下,虽然我是异步执行的,但是我承诺在干完该干的事情之后,我会把承诺的事情做完。

浏览器对Promise 的支持

因为Promise 是在ECMAScript 6才开始出现的,所以对于一些早期的浏览器版本是不支持的,对于
Safari 10,Windows 的 Edge 14以上的浏览器支持。完整的支持参考下表:
在这里插入图片描述

ECMAScript 6的Promise

  • Promise 是ECMAScript 6的一个类
  • Promise 对象表示异步操作的最终完成(或失败)及其结果值。
  • Promise 对象具有静态方法和原型方法,静态方法可以独立应用,原型方法需要应用于Promise对象的示例。
    当创建一个Promise 时,它处于pending状态,Promise 根据是否fulfilled(已成功)或rejected(已失败),将运行以下三种方法中的一种或多种:
  • Promise.prototype.catch(onRejected)
  • Promise.prototype.then(onFulfilled, onRejected)
  • Promise.prototype.finally(onFinally)
    下图显示了 then 和 .catch 方法的流程。由于它们返回一个 Promise ,它们可以再次被链式调用。不管 promise 最后的状态,在执行完then 或 catch 指定的回调函数以后,都会执行finally方法指定的回调函数。
    在这里插入图片描述
Promise 的状态

Promise 有3种状态:

  1. Pending, 进行中
  2. Resolved(Fullfilled),已完成
  3. Rejected,已失败
    在早期的Promise,完成的状态是Fullfilled,后来习惯就称为Resolved。

Promise 状态的改变:

  1. Pending -> Resolved
  2. Pending -> Rejected
    在这里插入图片描述
链式调用

通过多次调用then() 可以添加多个回调函数,它们按照插入顺序执行。
Promise链可以实现在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。

在没有Promise之前,多重异步操作,会导致经典的回调地狱:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

使用Promise , 形成一个Promise 链

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

catch(failureCallback) 是 then(null, failureCallback) 的缩略形式
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。

多说一点, 上面的写法也可以使用箭头函数进一步简化:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。

Promise.resolve() 和 Promise.reject()

两者都是new promise() 的快捷方式。

  1. Promise.resolve(value)
    等同于
    new Promise(function (resolve) {
      resolve(value)
    })
  1. Promise.reject(value)
    等同于
    new Promise(function(resolve,reject){
        reject(new Error("xx"));
    });
Promise.all() 和Promise.race()
  • Promise.all() 接收一个Promise对象的数组作为参数, 当数组里的所有Promise对象全部变为resolve时, 该方法才resolve;如果其中一个Promise对象reject, 则该方法reject。
Promise.all([
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(100);
         },100);
     }),
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(200);
         },200);
     }),
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(300);
         },300);
     })
 ]).then(function(value){
     console.log(value);  //[100,200,300]
 });
  • 接收一个Promise对象的数组作为参数, 只要一个Promise对象变为Resolved或者Rejected状态,该方法返回,进行后面的处理。
Promise.race([
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(100);
         },100);
     }),
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(200);
         },200);
     }),
     new Promise(function(resolve){
         setTimeout(function(){
             resolve(300);
         },300);
     })
 ]).then(function(value){
     console.log(value);  //100
 });

使用Promise 封装AJAX

上面一直说Promise 可以让异步的回调使用同步的方式书写,典型的就是在AJAX中使用, 那么AJAX是如何结合Promise 使用的呢? 看如下代码:

function ajaxGet(url) {
    // 返回一个Promise对象
    return new Promise(function (resolve, reject) {
        // 创建一个XHR对象
        var req = new XMLHttpRequest() || new ActiveXObject('Microsoft.XMLHTTP')
        req.open('GET', url, true)
        // 注意:使用req.onload监听req的状态
        req.onload = function () {
            if (req.readyState == 4 && req.status == 200) {
                resolve(req.response)
            } else {
                reject(Error(req.statusText))
            }
        }

        // 网络错误
        req.onerror = function () {
            reject(Error("Network Error"));
        };

        // 发送请求
        req.send();
    });
}

调用AJX的方式可以简化为:

ajaxGet("http://xxx").then(function (response) {
    console.log(response);
}).catch(function (error) {
    console.log(error);
})

注意:

  • 在AJAX中使用的事件是onreadystatechange, 这里使用的是onload。为什么不使用
    onreadystatechange呢?因为Promise 的状态改变时单向的,一次性的, 一旦改变,状态就会凝固,也即是req.onreadystatechange只会被调用一次。如果用onreadystatechange

Promise 的典型使用场景

Promise 应用在很多功能场景中, 包括:

  • 文件操作
  • API调用
  • DB调用
  • IO调用
  • Node.js 基于Promise

Promise 实现原理解密

说了这么多, 感觉Promise 很强大,也很神秘, 其实Promise 也不是那么神秘,简单的模拟一下Promise的实现:

function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        if (state === 'pending') {
            deferreds.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };

    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }

    fn(resolve);
}
  • Promise 有三个变量: 状态 state ,值value和延迟队列deferreds 。
  • 初始的状态是pending ,value是空,延迟队列也是空
  • 传入Promise 的函数fn立即执行,fn执行完成调用resolve
  • resolve方法采用异步方式执行延迟队列里的方法
  • promise对象上添加then方法,当前promise对象状态为pending时,将通过then方法注册的新方法,添加到延迟队列;当前promise对象状态为完成时,执行注册的方法

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oscar999

送以玫瑰,手留余香

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值