从手写一个符合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 就完成啦


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

查看所有标签

猜你喜欢:

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

Programming Amazon Web Services

Programming Amazon Web Services

James Murty / O'Reilly Media / 2008-3-25 / USD 49.99

Building on the success of its storefront and fulfillment services, Amazon now allows businesses to "rent" computing power, data storage and bandwidth on its vast network platform. This book demonstra......一起来看看 《Programming Amazon Web Services》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

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

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具