从手写一个符合Promise/A+规范Promise来深入学习Promise

栏目: JavaScript · 发布时间: 6年前

内容简介:一项技术不会凭空产生,都是为了解决某些实际的问题而出现。了解技术产生的背景,可以让我们更好的知道他擅长解决什么问题,哪些场景我们可以利用他来解决。那么就让我们一步一步来揭开promise神秘的面纱。首先我们来了解一下promise。promise英语的意思是:诺言。Promise是抽象异步处理对象及其对其进行各种操作的组件。用大白话说,promise就是用来解决异步回调问题的。在没有promise 之前,前端处理ajax请求通常是这样的:

一项技术不会凭空产生,都是为了解决某些实际的问题而出现。了解技术产生的背景,可以让我们更好的知道他擅长解决什么问题,哪些场景我们可以利用他来解决。那么就让我们一步一步来揭开promise神秘的面纱。

1.1.什么是promise

首先我们来了解一下promise。promise英语的意思是:诺言。Promise是抽象异步处理对象及其对其进行各种操作的组件。用大白话说,promise就是用来解决异步回调问题的。

1.2.promise解决了什么问题

在没有promise 之前,前端处理ajax请求通常是这样的:

function fetchData (callback) {
    $.ajax({
    type: 'GET',
    url:'xxx',
    data: data,
    success: function (res) {
     callback(res)   // 回调函数
    }
    })
}
复制代码

这只是处理一个ajax请求,可很多时候,我们回面临第一个请求回来的结果,是第二次请求的参数,或者我们想一个极端的例子,前一次请求的结果是第二次请求的参数,那么我们的代码可能是这样的:

function fetchData (callback) {
    $.ajax({
        type: 'GET',
        url: 'xxx',
        data:data,
        success: function (res) {
            if (res.data) {
            const params1 = res.data;
                $.ajax({
                    type:'GET',
                    url: 'xxx',
                    data:  params1,
                    success: function (res) {
                        if (res.data) {
                            const params2 = res.data
                            $.ajax({
                                type:'GET',
                                url: 'xxx',
                                data:  paramsw, 
                                success: function (res) {
                                    if (res.data) {
                                        const params3 = res.data;
                                        ......
                                    }
                                }
                            })
                        }
                    }
                })
            }
        }
    })
}
复制代码

上面这个就是回调地狱。代码的可读性,可维护性大打折扣。promise就是为他而生的,通过promise我们可以像写同步那样写异步方法。同样是上面的场景我们完全可以用另一种方法:

function fetchData (url, data) {
    return new Promise((resolve, reject) => {
       $.ajax({
        type: 'GET',
        url:url,
        data: data,
        success: function (res) {
         resolve(res)
        },
        fail: function (err) {
            reject(res)
        }
        }) 
    })
}

fetchData(url,data).then(res => {
    const url1 = 'yyyy';
    const data1 = res.data;
    return fetchData(url1,data1)
}).then(res => {
    const url2 = 'zzz';
    const data2 = res.data
    return fetchData(url2, data2)
}).then(res => {
    const url3 = 'wwww';
    const data3 = res.data;
    render()
}).catch(err => {
    console.log(err)
})
复制代码

这样似乎美观了那么一丢丢,但是不要着急,我们一步一步来。

2.promise的实际应用

上一章我们主要探讨了promise的背景知识,简单的知道了promise使用的场景,这章我们就来跟进一步的学习promise

2.1 promised的状态

promise有三种状态:

  • pending
  • fulfilled
  • rejected 如下图:
    从手写一个符合Promise/A+规范Promise来深入学习Promise
    promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。

2.2 promise是异步的么?

如下代码:你觉得输出的结果是什么呢?

var promise = new Promise(function (resolve){
    console.log("1"); //1
    resolve(2); 
});
promise.then(function(value){
    console.log(value); // 2
});
console.log("3");  // 3
复制代码

实际上执行上面的代码会输出下面的内容

1 
3
2
复制代码

你会很好奇为什么会是这样?

由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始 <1> 会执行,然后是 resolve(2); 被执行。这时候 promise 对象的已经变为确定状态,FulFilled被设置为了 2 。

由于 promise.then 执行的时候promise对象已经是确定状态,从程序上说对回调函数进行同步调用也是行得通的。

但是即使在调用 promise.then 注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。

因此 <3> 会最先被调用,最后才会调用回调函数 <2> 。

2.2 promise的链式调用

在前面的<1.2>中我们简单介绍了下promise的链式调用,在这里我们再次深入理解一下 看下面的代码

function task () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('task')
        },1000)
    })
}

task().then((res) => {
    console.log(res)
    return 'taskB'
}).then(res => {
    console.log(res)
    return 'taskC'
}).then(res => {
    console.log(res)
    throw new Error()
}).catch(e => {
    console.log(e)
    return 'taskD'
}).then(res => {
    console.log(res)
})
复制代码

哈哈,那么问题来啦?上面会输出什么呢?

从手写一个符合Promise/A+规范Promise来深入学习Promise

哈哈,是不是很困惑catch后怎么又进入then()了呢? 实际上前面我们已经说过,promise有三种状态pending,fulfilled,rejected,一旦从pending态到fulFilled,或者从pending态到rejected,其状态都是不可逆的。因此,.then()中每次return出去的都是一个新的promise,而不是this. 有图有真相(虽然图是我盗的,说明问题就好):

从手写一个符合Promise/A+规范Promise来深入学习Promise

那么对于异常情况下promise的流程是这个样子的:

从手写一个符合Promise/A+规范Promise来深入学习Promise

3.尝试写出符合Promse/A+规范的promise

前两小节介绍了promise 的背景,及promise的更深入的学习,这节我们通过自己动手实现一个符合promise/A+规范的promise来彻底弄懂promise

3.1 promise 构造函数

通过2.2的探究我们知道当你创建一个new Promise()的时候,实际上在new Promise()内部是同步执行的,也就是说

const promise = new Promise((resolve,reject) => {
    console.log(1)
})
复制代码

当代码执行到console.log(1) 的时候,1会被立马打印出来。也就是说,promise的构造函数中有一个executor的函数,他会立马执行。因此:

function Promise (executor) {
    executor()
}
复制代码

我们有向executor中传入了两个函数,resolve和reject,因此我们来进一步完善我们的构造函数:

function Promise(executro) {
    function resolve () {}
    function reject () {}
    executor(resolve, reject)
}
复制代码

我们知道promise中有三种状态,那么我们需要一个属性来控制这个状态,我们添加一个status的属性吧:

function Promise(executor) {
    let self = this;
    self.status = 'pending' // 初始状态为pending
    function resolve() {}
    function reject() {}
}
复制代码

当执行resolve函数的时候,status的状态应该从penging态转为resolved,同时存储resolve函数的值,同样的当执行reject函数的时候,status的状态应该从pengding态转为rejected,同时我们存储reject的值,代码入下:

function Promise (executor) {
    let self = this;
    self.status = 'pending'
    self.value = undefined
    self.reason = undefined
    self.onResolved = [] // 3.2小节添加
    self.onRejected = [] // 3.2小姐添加
    function resolve (value) {
        if (self.status === 'pending') {
            self.status = 'resolved'
            self.value = value
            self.onResolved.forEach(fn => fn())
        }
    }
    
    function reject (reason) {
        if (self.status === 'pending') {
            self.status = 'rejected'
            self.reason = reason
            self.onRejected.forEach(fn => fn())
        }
    }
    
    // 处理下异常
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}
复制代码

nice ^_^ 现在我们的Promise的构造函数就基本能满足我们的需求啦。

3.2 promise then方法

Promise/A+规范中,每个promise都有一个then方法,该方法返回的还是一个promise.这个then方法实在实例上调用,因此该then方法应该在promise的原型上,同样的我们需要根据promsie的状态来进行相应的处理,不同的是,当status是pending态的时候我们要收集resolve和reject,同时在构造函数中增加相应的处理,代码如下:

Promise.prototype.then = function (onfulfilled, onrejected) {
    // 这里坐下简单的处理
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : val => val
    onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err}
    
    let promise2 = new Promise((resolve, reject) => {
        if (self.status === 'resolved') {
            // onfulfilled(self.value) 3.2
            let x = onfulfilled(self.value) // 3.3
            resolvePromise(promise2, x, resolve, reject) // 3.3
        }
        
        if (self.status === 'rejected') {
            // onrejected(self.reason) 3.2
            let x = onrejected(self.reason) // 3.3
            resolvePromise(promise2, x, resolve, reject) // 3.3
        }
        
        if (self.status === 'pending') {
            self.onResolved.push(function () {
                setTimeout(() => {
                    // onfulfilled(self.value) 3.2
                    let x = onfulfilled(self.value) // 3.3
                    resolvePromise(promise2, x, resolve, reject) // 3.3
                },0)
            })
            
            sef.onRejected.push(function () {
                setTimeout(() => {
                    // onrejected(self.reason) 3.2
                    let x = onrejected(self.reason) // 3.3
                    resolvePromise(promise2, x, resolve, reject) // 3.3
                }, 0)
            })
        }
    })
    return promise2
}
复制代码

我们的then 函数就实现啦,最重要的就是对pending态的处理

3.3 onfullfiled和onrejected返回结果的处理

我们在3.2小节留了一个坑,你会发现,新的Promise构造函数中传入的resolve,reject好像没有用到啊。那么这小节就是要填这个坑的。那我们会发现then方法中的onResolved和onRejected实际上分别是在Promise构造函数中的resolve 和reject中处理的。实际上我们会面临以下集中情况:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    },0)
})

promise.then((res) => {
    return res
},(err) => {
    // 直接 reject
    reject(err)
}).then(res => {
    // 1. 直接返回一个变量
    return res
    // 2. 返回一个对象或者函数
    return obj
    // 3. 循环引用
})
复制代码

因此我们对3.2的代码改造,我直接写在3.2的代码中加上3.3的标记,我们还要给出对不同返回值的解析处理

function resolvePromise(promise2, x, resolve, reject) {
    // 判断是否循环引用
    if (x === promise2) {
       return reject(new TypeError('循环引用')) 
    }
    
    // 判断是否为对象或者函数
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
          let then = x.then
          // 判断是否为promise
          if (typeof then === 'function') {
              then.call(x, (y) => {
                  resolvePromise(promise2, y, resolve, reject)
              }, (e) => {
                  reject(e)
              })
          } else {
              resolve(x)
          }
        } catch (e) {
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

这下我们的promise 就完成啦


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

新媒体营销概论

新媒体营销概论

秋叶、刘勇 / 人民邮电出版社 / 2016-12-1 / 36.00

本书共分6章。第1章重点介绍了新媒体的概念和特征,引导读者全面认识新媒体所处的行业;第2章用历史发展的眼光,介绍了不同类型的新媒体,让读者不仅能看到最新的新媒体模式,也能看到这个模式发展背后的脉络;第3章重点介绍了新媒体广告投放载体,便于读者选择适合自己的新媒体运营方式;第4章介绍了新媒体运营的策划思维;第5章介绍了新媒体舆情管理知识;第6章选取了可口可乐、海底捞、恒大冰泉等的新媒体助力传统行业转......一起来看看 《新媒体营销概论》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换