那些年我们泡过的Promise

综合技术 2018-05-18 阅读原文

时间就好比一把杀猪刀,就一个promise折腾了我们大半个青春。

第一次邂逅,回调之美

当我们第一次遇到回调的时候,觉得她是个神奇的东西。就跟看到心动的姑娘一样,想接近她,认识她,交接她,然后拿下她。然而一切才刚开始。。。

我们可以先给故事的场景布置场景

故事1:什么是异步?

  • js是单线程
  • 队列是先进先出
  • 栈是先进后出

一切基于回调。我们知道函数可以像其他变量一样作为参数传递给函数。这就是回调诞生的原因。异步编程中,都是基于回调的。js中常见异步有:定时器,事件回调,ajax请求。那我们又该怎么来理解回调的概念呢?

第一次表白相信大家都很印象深刻。那么异步就是我们下面的女猪脚(异步是指被调用者)

男孩羞涩的走到女孩面前,想说什么又欲言而止。手中紧巴巴的捏着一盒益达。突然,男孩子鼓起勇把手中益达递到女孩面前。

男孩: “诶,你的益达。”

女孩接过手中的益达,轻轻的嗯了一声。

男孩:“喜欢这个口味吗?我喜欢你。”

女孩子当然不傻,心里想到:“想套路我没门”。

女孩说道:“让我想想”。

女孩子这种做法就是异步。为什么? 女孩子的答案 在这里就是 回调 ,女孩子没有立即去答应这件事。在这个期间女孩可以做其他,比如和别人约会,等哪天心情好了,再告诉男孩,我拒绝你了。

故事2:回调的缺点

继上面的故事,女孩子答应了男孩子的告白。在相处就是矛盾的开始,那小女孩有哪些小脾气呢?

  • 回调地狱,代码不利于维护,阅读。
  • 异步不利于捕获异常

我们的爱情有很多“承诺”--promise

promise是我们今天的重要内容。我们将通过自己来模仿理解promise。既然是承诺,我们就跟规范来一步一步实现自己的promise。具体可以参见promise/A+。

老规矩先放规则

  • promise即是对象也是函数。
  • then即是对象也是函数。
  • then的参数value可以是undefined, thenable, promise.
  • promise只能是其中一种状态。(pending, fulfilled, rejected)
  • 初始化态是pending,一旦发生改变,就不能在改变。

下面开始我们的第一段代码:

// 先抛个基础用法
let promise = new Promise((resolve, reject) => { 
   setTimeout(() => {
        resolve(123);
    });
});
promise.then((data) => {    console.log(data);})

1.我们先构建一个属于我们 MyPromise类

class MyPromise {
    ...}

2.根据规范我们初始化我们的参数

// 1.创建类
class MyPromise {
    constructor() {
        // 2.等待状态
        this.status = "pending"; 
       // 初始化data
        this.data = undefined;
        // 初始化reason
        this.reason = undefined;
    }
}

3.传入我们的执行函数executor,定义我们的resolve,reject回调。

// 1.创建类
class MyPromise {
    constructor(executor) {
        // 2.等待状态
        this.status = "pending";
        // 初始化data
        this.data = undefined;
        // 初始化reason
        this.reason = undefined;
        // 3.执行函数executor,定义我们的resolve,reject回调
        let resolve = (data) => {
            // 为了防止多次改变状态
            if(this.status === 'pending') { 
               this.status = 'resolved'
                this.data = data;
            }
        }
        let reject = (reason) => { 
           // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'rejected'
                this.reason = reason;
            }
        }
        try {
            executor(resolve, reject);
        } catch(err) {
            reject(err);
        }
    }
}

4.在原型上创建一个then方法

// 1.创建类
class MyPromise {
    constructor(executor) {
        // 2.等待状态 
       this.status = "pending";
        // 初始化data 
       this.data = undefined;
        // 初始化reason
        this.reason = undefined;
        // 3.执行函数executor,定义我们的resolve,reject回调
        let resolve = (data) => {
            // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'resolved'
                this.data = data;
            }
        }
       let reject = (reason) => { 
           // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'rejected' 
               this.reason = reason;
            } 
       }
        try {
            executor(resolve, reject);
        } catch(err) {
            reject(err);
        }
    }
    // 4.在原型上添加then方法
    then(onFulFilled, onRejected){
        if(this.status === 'resolved'){
            onFulFilled(this.data);
        }
        if(this.status === 'rejected'){
            onRejected(this.reason)
        }
        if(this.status === 'pending'){
            // 这里要做一件很有意思的事。。。。
        }
    }
}

现在我们就是实现了一个简单的promise,大家可以简单跑一下。

回过头想一哈,我们的写得代码都是同步,如何来实现promise的异步呢?想想设计模式中的观察模式。是不是我们先把回调存起来,然后到达一定条件再去调用了。说干就干,绝不含糊。

5.实现简单的异步代码

// 1.创建类
class MyPromise {
    constructor(executor) {
        // 2.等待状态
        this.status = "pending";
        // 初始化data
        this.data = undefined;
        // 初始化reason
        this.reason = undefined;
        // 3.执行函数executor,定义我们的resolve,reject回调
        let resolve = (data) => {
            // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'resolved'
                this.data = data;
                this.onFulFilledCallbacks.forEach((fn) => {
                    fn()
                })
            } 
       }
       let reject = (reason) => { 
           // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'rejected'
                this.reason = reason;
                this.onRejectedCallbacks.forEach((fn) => {
                    fn()
                })
            }
        }
        // 5.储存fulfilled的回调
        this.onFulFilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            executor(resolve, reject);
        } catch(err) {
            reject(err);
        }
    }
    // 4.在原型上添加then方法
    then(onFulFilled, onRejected){
        if(this.status === 'resolved'){
            onFulFilled(this.data);
        }
        if(this.status === 'rejected'){
            onRejected(this.reason)
        }
        if(this.status === 'pending'){
            // 这里要做一件很有意思的事。。。。
            this.onFulFilledCallbacks.push(() => {
                onFulFilled(this.data);
            })
            this.onFulFilledCallbacks.push(() => { 
               onRejected(this.reason)
            })
        }
    }
}

6.现在我们要考虑的是如何做链式调用。来读读规范then需要返回一个什么值呢?

返回的是promise对象。也就是说onFulfilled,onRejected的返回值x决定我们下一个promise的状态。通过resolvePromise方法解析返回值x。

// 解析promise对象
function resolvePromsie(promise, x, resolve, reject){
    // 规范说先promise是不是来自同一个对象,并返回一个理由
    // If promise and x refer to the same object, reject promise with a TypeError as the reason.
    try {
        // If promise and x refer to the same object, reject promise with a TypeError as the reason.
        // 如果是相同引用就抛出错误
        if(promise === x) return reject(new TypeError('不能循环引用'));
        // if x is an object or function,
        // 如果是对象,或是函数,我们就要看then是不是函数
        if(x != null && (typeof x === 'object' || typeof x === 'function')){
            // Let then be x.then
            let then = x.then;
            if(typeof then === 'function'){
                // If then is a function, call it with x as this,
                // first argument resolvePromise, and second argument rejectPromise, where:
                then.call(x, y => {
                    resolvePromsie(promise, y, resolve, reject);
                }, (err) => {
                    reject(err);
                })
            }else{
                resolve(x);
            }
        }else{
            resolve(x);
        }
    } catch (err) {
        reject(err)
    }
}
// 1.创建类class MyPromise {
    constructor(executor) {
        // 2.等待状态
        this.status = "pending";
        // 初始化data
        this.data = undefined;
        // 初始化reason
        this.reason = undefined;
        // 3.执行函数executor,定义我们的resolve,reject回调
        let resolve = (data) => {
            // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'resolved'
                this.data = data;
                this.onFulFilledCallbacks.forEach((fn) => {
                    fn()
                })
            }
        }
       let reject = (reason) => {
            // 为了防止多次改变状态
            if(this.status === 'pending') {
                this.status = 'rejected'
                this.reason = reason;
                this.onRejectedCallbacks.forEach((fn) => {
                    fn()
                })
            }
        }
        // 5.储存fulfilled的回调
        this.onFulFilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            executor(resolve, reject);
        } catch(err) {
            reject(err);
        }
    }
    // 4.在原型上添加then方法
    then(onFulFilled, onRejected){
        // 处理无参数时的问题
        onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
        // 抛出错误直接丢到下一个then中
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
      // 规范里取得名字,不是瞎搞的哈!!!
        let promise2;
        if(this.status === 'resolved'){
            promise2 = new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onFulFilled(this.data);
                        resolvePromsie(promise2, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                }, 0);
            })
        }
        if(this.status === 'rejected'){
            promise2 = new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        // If either onFulfilled or onRejected returns a value x,
                         // run the Promise Resolution Procedure [[Resolve]](promise2, x).
                        let x = onRejected(this.reason);
                        resolvePromsie(promise2, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                }, 0)
            })
        }
        if(this.status === 'pending'){
            // 这里要做一件很有意思的事。。。。
            promise2 = new MyPromise((resolve, reject) => {
                // 将回调放在数组中等待调用
                this.onFulFilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulFilled(this.data);
                            resolvePromsie(promise2, x, resolve, reject);
                        } catch (err) {
                            reject(err);
                        }
                    }, 0)
                  })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromsie(promise2, x, resolve, reject);
                        } catch (err) {
                            reject(err);
                        }
                    }, 0)
                })
            })
        }
        return promise2;
    }
}

promise的原理思路大概就是这样的。关于挂在promise原型上的方法大家可以自思考。只要总体实现了,就用已有的简单几行代码就搞定了。

大婚之喜,蜜月执行--generator

es6提供了一个生成器函数 generator

  • 生成器函数需要加*,返回一个迭代器。
  • 生成器函数和普通函数的区别,普通函数是一旦调用就会执行完,但是生成器函数可以暂停
  • 通过yield来暂停,每次调用next时,遇到next就暂停,返回当前值。

写个小demo来了解,迭代器函数[Symbol.iterator].要想迭代必须要有这么个函数,并且要遵循它的规则。迭代器函数[Symbol.iterator]必须返回一个对象,对象又必须包括next()的函数,next函数又要返回{value: 当前值, done: 布尔值表示是否迭代完成}。

let likeArray = {
    0: 1,
    1: 2,
    2: 3,
    length: 3,
    [Symbol.iterator](){
        let index = 0;
        let len = this.length;
        let that = this;
        return {
            next(){
                return {
                    value: that[index],
                    done: index++ === len
                }
            }
        }
    }}
let arr = [...likeArray];console.log(arr);

接下写个generator例子解释执行过程。

function *gen() {
    let a = yield 1;
    let b = yield a;
    return b;
}
let a = gen();
console.log(a.next()); //  {value: 1, done: false }
console.log(a.next(11)); // { value: 11, done: false }
console.log(a.next(22)); // { value: 22, done: true }

通过这图就能了解到生成器的使用秘诀了。在异步中我们通常可用generator+co库来一起完成。co是 TJ 大神结合了promise 和 生成器 的一个库,实际上还是帮助我们自动执行迭代器。下面让我们自己来完成一个co的实现

function co(generator){
    // co返回一个promise对象
    return new promise((resolve, reject) => {
        let fn = generator(); 
        // 递归自己完成,最后把值扔出去
        function next(data){
            let { value, done } = fn.next(data);
            if(done) return resolve(value);
            value.then((data) => {
                next(data)
            },(err) => {
                reject(err)
             })
        };
        next();
    })
}

七年之痒,磨合终成正果--async/await

es7中引入了async/await,其实是generator+promise实现的语法糖。async函数就是将Generator函数的星号(*)替换成async,将yield替换成await。

async function r() {
  let b = await Promise.all([read('a.txt', 'utf8'), read('b.txt', 'utf8')]);
  return b;
};

这样代码看起来就更优雅,语义化更好。

后续:

通过写这篇文章,能带给大家一些帮助。有问题,还希望大大多多指点。同时祝天下有情人终成眷属。

看好你的姑娘,小心我来了。。。

稀土掘金

责编内容by:稀土掘金阅读原文】。感谢您的支持!

您可能感兴趣的

关于Promise知识点都在这里 可以支持多个并发的请求,获取并发请求中的数据; a().b()//比如这个a执行完了,执行b ajax()//1 ajax()//2 可以解决异步的问题,本身不能说promise是异步的(then方法是异步的) promise(承诺)关键词 reso...
实现一个玩具 Promise ~ hello~亲爱的观众老爷们大家好,有段时间没写文章了,最近主要忙于工作交接,实在没腾出时间进行总结。这次为大家带来 es6 中 Promise 的简单实现。 事实说类似的文章已经不少,不少大神对此都有精彩的实现。然而自己消化了才是最好的,在看文章中其实还是遇到不少坑,如 t...
用ES6实现一个简单易懂的Promise(遵循Promise/A+ 规范并附详解注释)... 一.Promise的含义和意义 1.什么是Promise Promise是抽象异步处理对象以及对其进行各种操作的组件,其实Promise就是一个对象,用来传递异步操作的消息,它不是某门语言特有的属性,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象,Promise对象...
Promise并行,串行 这个功能Promise自身已经提供,不是本文的重点。主要是依赖Promise.all和Promise.race。 Promise.all是所有的Promise执行完毕后(reject|resolve)返回一个Promise对象。 Promise.race是任意一个Promise对象执...
Angular: assign and access promise via r... I would like to use a promise and access it through a rootScope instead of using return statement. The problem I am having is that promise is getti...