阅读 205

从 Promises/A+ 规范康一康 Promise

先进行一些基本的概念梳理:

什么是 Promise?

Promise 是异步编程的一种解决方案:从语法上讲,Promise是一个对象,它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。

Promise 可以解决什么问题?

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
    当异步场景越来越多的时候,代码会横向发展(变宽),依赖的层级逐渐变多,会不停的进行嵌套 ,最终导致回调地狱。Promise实际上是使异步调用更加链式了,可以将异步操作队列化,按照期望的顺序依次进行执行,返回符合预期的结果。
  • Promise可以支持多个并发的请求,获取并发请求中的数据
  • Promise可以解决异步的问题,但本身不能说 promise 是异步的

举个 🌰

//回调函数
const callback=function(){
    console.log('callback')
}
function callbackFn(fn){
    setTimeout(fn,3000)
}
callbackFn(callback)
//封装成promise
const promise =new Promise(function(resolve,reject){
    console.log('success')
    resolve()
})
promise.then(
    function(){
        setTimeout(callback,3000)
    }
)
复制代码

Promise 优缺点

优点

  • 把执行代码和处理代码分离开,使异步操作逻辑更加清晰。
  • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据)
  • 对象提供统一的接口,使得控制异步操作更加容易
  • 代码风格,容易理解,便于维护

缺点

  • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段

Promise 三种状态

  • 初始状态:pending(等待态)
  • 操作成功:fulfilled(成功态)
  • 操作失败:rejected(失败态)

Promise 使用

Promise的构造函数接收一个函数作为参数,并且这个函数需要传入两个参数:

  • resolve(): 异步操作执行成功后的回调函数,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
  • reject(): 异步操作执行失败后的回调函数,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。在执行resolve的回调时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。
  • Promise状态发生改变,就会触发then()里的响应函数处理后续步骤;创造Promise实例后,它会立即执行,Promise状态一经改变,不会再变。
  • 当出现.then()里面有.then()的情况时,由于.then()返回的还是 Promise 实例,会等里面的.then()执行完,再执行外面的
let p = new Promise((resolve, reject) => {
        //做一些异步操作
      setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的随机数
            if(num<=5){
                resolve(num);
            }
            else{
                reject('数字太大了');
            }
      }, 2000);
    });
    p.then((data) => {
            console.log('resolved',data);
        },(err) => {
            console.log('rejected',err);
        }
    );
复制代码

Promises/A+ 规范(划重点)*

其实归根结底,Promises/A+ 规范为整个Promise的执行和使用提供了完整的解释,所有对Promise的疑问都能在这里得到解答。
先上一个官方地址:promisesaplus.com

由于官网是英文版本,为了便于阅读和理解,结合 Google 翻译自己转译了一下,水平有限,有问题欢迎指正,感恩的心 💗💗

规范正文:(举 🌰 补充)

        一个由实施者提供的开放的,可实现的,可互操作的 JavaScript 承诺的标准,对于实施者。

        一个Promise表示异步操作的最终结果。与承诺进行交互的主要方式是通过其then方法,该方法注册回调以接收承诺的最终值或无法实现承诺的原因。

        该规范详细介绍了该then方法的行为,提供了可互操作的基础,所有Promises / A +兼容的Promise实现都可以依靠该基础来提供。因此,该规范应被认为是非常稳定的。尽管 Promises / A +组织有时会通过向后兼容的微小更改来修订此规范,以解决新发现的极端情况,但只有经过仔细考虑,讨论和测试之后,我们才会集成大型或向后兼容的更改。

        从历史上看,Promises / A +阐明了较早的Promises / A提案的行为条款,将其扩展为涵盖事实上的行为,并省略了未指定或有问题的部分。

        最后,核心的Promises / A +规范不涉及如何创建,履行或拒绝诺言,而是专注于提供一种可互操作的then方法。未来工作中伴随着这些规范可能涉及到这些主题。

术语
  • Promise是具有 then 方法的符合本规范的对象或函数。(狭义认为一个对象只要有 then 方法就是一个合法的 promise 对象)
  • thenable(包含then方法)是定义then方法的对象或函数。
  • value(值)是任何合法的JavaScript值(包括undefinedthenablepromise)。
  • exception(异常)是使用该 throw 语句引发的值。
  • reason(原因)是表明拒绝承诺的原因的值。
要求
promise 的状态

一个promise必须处于以下三种状态之一:等待中(pending),已完成(fulfilled)或已拒绝(rejected)。

  • 处于等待中:
    • 可能会转换为已完成或已拒绝状态。
  • 当已完成时:
    • 不得过渡到任何其他状态。
    • 必须具有一个值,该值不能更改。
  • 当已拒绝时:
    • 不得过渡到任何其他状态。
    • 必须有一个原因,不能改变。

在此,“不得更改”是指不变的身份(即===),但并不表示深层的不变性。

一个 then 方法

一个承诺必须提供一个then方法来访问其当前或最终值或原因。

Promisethen方法接受两个参数:

promise.then(onFulfilled, onRejected)

  • 这两个onFulfilledonRejected是可选的参数:
    • 如果 onFulfilled 不是函数,则必须将其忽略。
    • 如果 onRejected 不是函数,则必须将其忽略。
  • 如果 onFulfilled 是一个函数:
    • 必须在 promise 执行结束后调用,以promise` 的结果作为第一个参数。
    • promise 执行结束之前不能调用它。
    • 不能多次调用。
  • 如果 onRejected 是一个函数:
    • 必须在 promise 被拒绝之后调用,以 promise 的原因作为第一个参数。
    • promise 被拒绝之前不能调用它。
    • 不能多次调用。
  • onFulfilledonRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
  • onFulfilledonRejected 必须作为普通函数调用(即没有 this 值)(非实例化调用,this 在非严格模式下会指向 window)。
  • then 可能在同一 promise 中多次被调用。
    • promise 完成时,则所有相应的 onFulfilled 必须按照其注册顺序执行 then
    • promise 被拒绝时,则所有相应的 onRejected 必须按照其注册顺序执行 then

      promise2 = promise1.then(onFulfilled, onRejected);

  • then 必须返回 promise
    • 如果有一个 onFulfilledonRejected 返回一个值 x,promise2 进入 onFulfilled 状态。[1]
    • 如果任何一个 onFulfilledonRejected 引发一个异常 e,则 promise2 必须拒绝 e 并返回拒绝原因。[2]
    • 如果onFulfilled 不是函数且 promise1 已完成,则 promise2 必须使用相同的值来完成 promise1(成功)。[3]
    • 如果 onRejected 不是函数而 promise1 被拒绝,则 promise2 必须以与相同的理由将其拒绝(失败)。[4] (每一个 promise 只取决于上一个 promise 的结果)
对照上面四条标准,这个地方举几个 🌰 来看一下 promise 的输出:(判断一下以下几种情况分别输出什么?)

(1)

const promise1 = new Promise(function (resolve, reject) {
    reject()
})
promise1
    .then(function () {
        return 123;
    }, null)
    .then(null, null)
    .then(null, null)
    .then(function () {
        console.log('success promise')
    }, function () {
        console.log('error promise')
    })
复制代码

输出error promise

首先promise1进入onRejected状态,返回的值为null,不是一个函数,参照上方第[4]条,不停向下onRejected直到console.log('error promise') (2)

const promise1 = new Promise(function (resolve, reject) {
   reject()
})
promise1
    .then(null,function () {
        return 456;
    })
    .then(null, null)
    .then(null, null)
    .then(function () {
        console.log('success promise')
    }, function () {
        console.log('error promise')
    })
复制代码

输出success promise

首先promise1进入onRejected状态,函数返回一个值为 456,参照上方第[1]条,此时promise2进入 onFulfilled 状态,参照上方第[4]条,不停向下完成直到console.log('success promise')

(3)

const promise1 = new Promise(function (resolve, reject) {
    resolve()
})
promise1
    .then(null, function () {
        return 456;
    })
    .then(null, null)
    .then(null, null)
    .then(function () {
        throw Error('e')
    }, null)
    .then(function () {
        console.log('success promise')
    }, function () {
        console.log('error promise')
    })
复制代码

输出error promise

首先promise1进入onFulfilled状态,返回null,参照上方第[4]条,不停向下完成直到抛出异常e,参照上方第[2]条,此时进入onRejected状态,向下输出console.log('success promise')

promise 解决过程

promise 解决过程是一个抽象的操作,输入一个 promise 和一个值,它表示为[[Resolve]](promise, x)。如果 x 是 thenable 的,则其行为类似于 promise,尝试使 promise 采用 x 的状态。否则,它将使用 value

只要对约定的实现公开 Promises / A +兼容的 then 方法,对约定的实现就可以进行互操作。它还允许Promises / A +实现以合理的 then 方法“同化”不合格的实现。 要运行[[Resolve]](promise, x),请执行以下步骤:

  • 如果 promisex 引用相同的对象,则以 TypeError 为理由拒绝promise
  • 如果 x 是一个 promise,则采用其状态:
    • 如果 x 等待中,则 promise 必须保持等待状态,直到 x 已完成或已拒绝。[1]
    • 如果 x 已完成,promise 则以相同的值完成。[2]
    • 如果 x 已拒绝,promise 则以相同的理由拒绝。[3](.then()的状态取决于返回的 promise 的状态)
再举个 🌰
var promise1 =new Promise(function(resolve,reject){
    resolve('promise1')
})
var promise2 =new Promise(function(resolve,reject){
    resolve('promise2')
})
promise1.then(function(val){
    return promise2
}).then(function(val){
    console.log(val)
})
复制代码

输出promise2,参照上方第[2]条

  • 否则,如果 x 是对象或函数(不太常见)
    • 先执行 x.then
    • 如果取 x.then 中值时抛出异常的结果 e,以 e 作为原因拒绝 promise
    • 如果 then 是函数,请使用 x 作为 this,第一个参数 resolvePromise 和第二个参数 rejectPromise 进行调用,其中:
      • 如果 resolvePromise 调用 y 作为值,请运行[[Resolve]](promise, y)
      • 如果 rejectPromise 调用 r 作为拒因,以 r 作为原因拒绝 promise
      • 如果同时调用 resolvePromiserejectPromise,或者对同一个参数进行了多次调用,则第一个调用优先,其他任何调用都将被忽略。
      • 如果调用 then 引发异常 e
        • 如果 resolvePromiserejectPromise 已经被调用,则忽略它。
        • 否则,以 e 作为原因拒绝 promise
    • 如果 then 不是函数,promise 调用 x 变为已完成状态。
  • 如果 x 不是一个对象或函数,promise 调用 x 变为已完成状态(常见)。
又来一个 🌰
var promise1 =new Promise(function(resolve,reject){
    resolve('promise1')
})
var promise2 =new Promise(function(resolve,reject){
    resolve('promise2')
})
promise1.then(function(val){
    return val
}).then(function(val){
    console.log(val)
})
复制代码

输出promise1x不是一个对象或函数

如果使用在一个循环链中的 thenable 来解决 promise[[Resolve]](promise, thenable)会导致递归,[[Resolve]](promise, thenable)将被再次调用,以上会导致无限递归。推荐(但不是必需),检测这种递归并以TypeError 为理由拒绝 promise

手写一个Promise

根据promiseA+实现一个自己的promise,这里不多讲解了,文末会推荐几篇自己觉得比较好的文章

(其实是我自己在写的还没完善好,等写满意了再回来补坑)

介绍几个Promise常用的方法

Promise.all

谁跑的慢,以谁为准执行回调。all接收一个数组参数,里面的值最终都算返回Promise对象

并行执行异步操作的能力(没有顺序),并且在所有异步操作执行完后才执行回调。

const promise = function (time) {
    return new Promise(function (resolve, reject) {
        return setTimeout(resolve(time), time);
    })
}
Promise.all([promise(1000), promise(3000)]).then(function (values) {
    console.log('all', values.toString()) // 三个都成功则成功 
},function(e){
    console.log(e) // 只要有失败,则失败 
})
复制代码

3秒后输出 all 1000,3000

Promise.race

谁跑的快,以谁为准执行回调。

const promise = function (time) {
    return new Promise(function (resolve, reject) {
        return setTimeout(resolve(time), time);
    })
}
Promise.race([promise(1000),promise(3000)]).then(function(value){
    console.log('race',value)
})
复制代码

1秒后输出 race 1000

可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

    //请求某个图片资源
    function requestImg(){
        var p = new Promise((resolve, reject) => {
            var img = new Image();
            img.onload = function(){
                resolve(img);
            }
            img.src = '图片的路径';
        });
        return p;
    }
    //延时函数,用于给请求计时
    function timeout(){
        var p = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('图片请求超时');
            }, 5000);
        });
        return p;
    }
    Promise.race([requestImg(), timeout()]).then((data) =>{
        console.log(data);
    }).catch((err) => {
        console.log(err);
    });
复制代码

巨人的肩膀(包括认为比较好的手写Promise文章)

最后

欢迎纠错,看到会及时修改哒!
温故而知新,希望我们都可以保持本心,念念不忘,必有回响。

最后的最后

给自己的小组卖个安利,欢迎热爱前端的小姐妹们加入我们,一起学习,共同进步【有意请留言或私信,社畜搬砖不及时,但看到会立刻回复】 💗💗💗💗💗

本文使用 mdnice 排版