深入浅出FE(一)promise对象

目录

一、定义

二、关于Promise

三、事件循环中的Promise

四、Promise A+规范的实现

五、参考资料


一、定义

ECMA-262(ES6)给出的定义是:

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

翻译成中文就是:Promise对象是用来作为延迟(包含异步)运算的最终结果的占位符。

标准给出的定义是抽象的,我给出的定义是:

Promise对象是一个异步操作的容器,是一个包含对未来事件的结果(可以是异步的),是一个穿越时间循环存在的对象。Promise有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。并且Promise是异步编程的解决方案,或者说主要是用同步的方式来书写异步。

基本的概念本篇教程不再复述,本篇不是入门教程,如果你此前不了解Promise,请先自行了解。

二、关于Promise

1.首先Promise是一个构造函数,可以生成Promise实例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

2.Promise.prototype.then()和Promise.prototype.catch()

(1)resolved状态的Promise会回调后面的第一个then;

(2)rejected状态的promise会回调后面的第一个catch。

  (3)  then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。

/**
* 用setTimeout模拟异步操作
*/
const promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve(1);
    }, 500)
})

promise
    .then(function (result) {
        console.log(result)
    })
    .catch(function (err) {

    })

以上代码先返回一个pending状态的Promise,500ms后输出1,这也是一个事件循环,稍后会介绍。

3.任何一个rejected状态且后面没有catch的Promise都会造成浏览器(或者node)环境的全局错误,执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch回调函数的执行结果决定。

(1)若回调函数的最终结果是throw,则该promise是rejected状态;

(2)若回调函数的最终结果是return,则该Promise是resolved状态;

(3)若回调函数最终return了一个Promise,该Promise会和回调函数return的Promise的状态保持一致。

4.如果是多次异步调用,需要多个then,但是只需要一个catch。

任何一个then中返回的promise对象rejected都会被catch捕获。

/**
 * promise的链式调用
 */

function getResult(round) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.2) {
                const error = new Error('failed');
                error.round = round;
                reject(error);

            } else {
                resolve('success');
            }
        }, 500)
    })
}

getResult(1)
    .then(()=> {
        return getResult(2);
    })
    .then(()=> {
        return getResult(3);
    })
    .then(()=> {
        if (Math.random() > 0.1) {
            const error = new Error('keyboard')
            error.round = 'keyboard'
            throw error 
        }
    })
    .catch((err)=> {
        console.log('cry at ' + err.round)
    })

5.并发异步Promise.all,第一个catch只能捕获第一个失败的结果。

Promise.all([
    getDecide('father').catch(() => { }),
    getDecide('mother'),
    getDecide('wife'),
    
]).then(() => {
    console.log('family all agree')

}).catch((err) => {
    console.log(err.name + ' not agree');
})
/**
*获取是否同意结果
*/
function getDecide(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.2) {
                const error = new Error('disagree');
                error.name = name;
                reject(error);

            } else {
                resolve('agree');
            }
        }, Math.random() * 400)
    })
}

6.Promise.prototype.finally

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

Promise.prototype.finally = function (callback) {
    return this.then((value) => {
        return Promise.resolve(callback()).then(() => {
            return value;
        });
    }, (err) => {
        return Promise.resolve(callback()).then(() => {
            throw err;
        });
    });
}

7.Promise.race

当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolveed),也可以是失败(rejected),这要取决于第一个完成的方式是两个中的哪个。

如果传的参数数组是空,则返回的 promise 将永远等待。

如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            return;
        } else {
            for (let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then((data) => {
                    resolve(data);
                    return;
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

8.async/await的执行结果是一个Promise,是Promise的语法糖。async/await是异步编程的终极解决方案,以同步的方式写异步。

(1)使用try/catch可以捕获await的错误

(2)await可以以同步的写法获取Promise的执行结果

(3)await可以“暂停”async function的执行

(4)如果要并行异步,await Promise.all([异步1,异步2,...])

9.Promise.prototype.finally

ES8标准,Promise.prototype.finally方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在then()catch()中各写一次的情况。CSDN给出的例子请求数据中,不管请求成功与否都将请求标志设置为false。

let isLoading = true;

fetch(myRequest).then(function(response) {
    var contentType = response.headers.get("content-type");
    if(contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then(function(json) { /* process your JSON further */ })
  .catch(function(error) { console.log(error); })
  .finally(function() { isLoading = false; });

三、事件循环中的Promise

事件循环指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。"Event Loop是一个程序结构,用于等待和发送消息和事件。是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。

异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

事件循环中的异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务(macrotask):

script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

微任务(microtask):

Promise、 MutaionObserver、process.nextTick(Node.js环境)

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:

  • 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
  • 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
  • 更新render(每一次事件循环,浏览器都可能会去更新渲染)
  • 重复以上步骤

Promise属于微任务,所以会在执行完宏任务后执行。本文提到的一个例子:

const promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve(1);
    }, 500)
})

promise
    .then(function (result) {
        console.log(result)
    })
    .catch(function (err) {

    })

上面代码的执行结果是:先返回一个pending状态的Promise,然后输出1。

四、Promise A+规范的实现

根据promise A+规范实现promise,promise A+规范

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  _this.onFulfilled = [];
  _this.onRejected = [];

  function resolve(value) {
    if (value instanceof Promise) {
      value.then(resolve, reject);
    }
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = FULFILLED;
        _this.value = value;
        _this.onFulfilled.forEach(cb => cb());
      }
    });
  };
  function reject (reason) {
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.onRejected.forEach(cb => cb());
      }
    });
  };
  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e)
  }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
  let _this = this;
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
  onRejected = typeof onRejected === "function" ? onRejected : value => { throw value };

  let promise2 = new Promise((resolve, reject) => {
    if (_this.currentState === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onFulfilled(_this.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          reject(e);
        }
      });
    } else {
      if (_this.currentState === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(_this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(e);
          }
        });
      } else if (_this.currentState === PENDING) {
        _this.onFulfilled.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(_this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
        _this.onRejected.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(_this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
      }
    }
  });

  return promise2;
};

function resolvePromise(promise2, x, resolve, reject) {
  let _this = this;

  if (promise2 === x) {
    reject(new TypeError("Loop references"));
  }

  if (x && typeof x === 'object' || typeof x === 'function') {
    let flag;
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call( x, (y) => {
            if (flag) return;
            flag = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (flag) return;
            flag = true;
            reject(r);
          }
        );
      } else {
        if (flag) return;
        flag = true;
        resolve(x);
      }
    } catch (e) {
      if (flag) return;
      flag = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}


module.exports = Promise;

如果要对上面的promise实现测试,可以添加如下代码:

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

安装promises-aplus-tests并执行测试

npm install -g promises-aplus-tests
promises-aplus-tests promise.js

结果截图,即通过了872个测试用例:

五、参考资料

1.ECMAScript® 2015 Language Specification

2.Promise 对象,「阮一峰官方博客」

3.Promise,「廖雪峰官方博客」

4.https://juejin.im/user/5c6256596fb9a049bd42c770

5.Promise A+ 规范

6.promise,「MDN」

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

fullstack_lth

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值