深入promise

单线程

js是单线程模式的,试想在js当中同时有多个线程,其中有一个线程修改了某一个dom元素,而另外一个线程同时删除了这个元素,浏览器就无法确定以那一个线程结果为准,为了避免这种线问题,从一开始js就被设计为了单线程模式。这里单线程是js运行环境执行代码的线程只有一个,当有多个任务的时候,排队等待依次完成。

优缺点

优点:安全,简单
缺点:假如遇到一个特别耗时的任务,其他任务就被堵塞,整个程序出现假死情况。

同步/异步

为了解决这种问题,js将执行模式分为同步(synchronous)异步(asynchronous)

同步模式(synchronous)

指的就是我们代码当中的任务依次执行,后一个任务必须等待前一个任务结束才能执行,在单线程模式下,我们大多数任务会以同步模式去运行.
简单例子:

console.log('global begin')

function bar () {
    console.log('bar task')
}

function foo () {
    console.log('foo task')
}
bar()
foo()
console.log('global end')
//global begin
//bar task
//bar task
//global end

这种就是纯同步代码,就是顺序执行,如果遇到特别耗时的任务,就会堵塞。这种阻塞对于用户而言就意味着界面会卡顿,或者卡死,所以为了解决这个问题出现了异步模式。

异步模式(asynchronous)

与同步模式不同,异步模式的执行是不会等待这个任务的结束才会执行下一个任务,对于耗时操作都是开启过后就放到异步队列立即往后执行下一个任务,内部我们这个耗时任务完成过后就会自动执行我们这里传入的回调函数。
优点:解决同步线程堵塞的问题
缺点:代码执行的顺序的混乱,不像同步代码那样是顺序执行的了。
简单例子:

console.log('global begin')

setTimeout(function timer1 () {
    console.log('timer1 invoke')
}, 1800)

setTimeout(function timer2 () {
    console.log('timer2 invoke')

    setTimeout(function inner () {
        console.log('inner invoke')
    }, 1000)
}, 1000)

console.log('global end')
// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke

这里也是简单介绍开启异步的 像setTimeout是宏任务,promise是微任务,都会开启异步模式。有需要的可以再去了解下js的eventLoop(js事件循环机制),这里重点讲处理异步方法。

回调函数

就是你想做的事情,你也知道这件事你应该怎末做,但是不知道依赖的任务什么时候才完成,任务什么时候才会执行到你这个方法,最好的方法就是把这个任务写到一个函数中,交给执行者,他是知道这个任务什么时候结束的,他就可以在任务结束过后帮你去执行,你想要做的事情,那这个想要做的事情其实就可以理解为回调函数。
拿我们在拿程序当中的ajax请求为例:当我们调用ajax操作,目的就是为了拿到请求结果过后去做一些事情,例如我们将其显示到界面上,但是这个请求什么时候能完成我们并不知道,所以说我们需要把得到结果要去执行的任务定义到一个函数中,然后内部的ajax请求到数据过后,它会自动执行这个任务。
这种由调用者定义,交给执行者执行的函数,称之为回调函数。
**
简单例子:

function foo (callback) {
    setTimeout(function () {
        callback()
    }, 3000)
}

foo(function () {
    console.log('这就是一个回调函数')
    console.log('调用者定义这个函数,执行者执行这个函数')
    console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})

假如你请求a数据,b在a成功返回的时候执行,c依赖b的成功执行,d依赖c.....如果是普通的会容易写成回调函数的嵌套,多层的嵌套就是回调地狱。
而promise就是一种更优的解决方案。

promise

  • promise实际上就是一个对象,用来去表示一个异步任务,最终结束过后,他是成功还是失败

就像是内部对外界做出一个承诺,一开始这个承诺是一个待定的状态 Pending,最后有可能成功 fulfilled,也有可能失败 Rejected。

  • 承诺状态明确过后,不管是成功还是失败,都会有相对应的任务会被自动执行,而且这种承诺会有很明显的特点,一点明确了结果过后,不可更改。

例如:你需要我去帮你发送一次ajax请求,其实就可以理解为,我承诺帮你请求一个地址,这个请求有可能成功,然后调用 onFulfiled 的回调,如果失败就会调用 onRejected 的回调。

promise基本用法

const promise = new Promise(function(resolve, reject) {
    // "兑现" 承诺的逻辑
    // 承诺--------达成
    resolve(100)
    //承诺--------失败
    //reject(new Error('promise err'))
})
promise.then(function(value) {
    console.log("resoled",value)
},function(err) {
    console.log("rejected", err)
})

//resoled 100

你可以分别执行下resolve,和reject方法,看下它的执行结果,后面会具体说promise的resolve和reject。

promise的使用案例

function ajax (url) {
    return new Promise (function (resolve, reject) {
        let xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
            if(this.status === 200){
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

ajax('../api/users.json').then(function (res) {
    console.log(res)
}, function (error) {
    console.log(error)
})

Promise常见误区

function ajax(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('GET',url)
        xhr.responseType = 'json'
        xhr.onload = function() {
            if(this.status === 200) {
                resolve(this.response)
            }else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    }) 
}
ajax('api/user.json').then(function(urls) {
    ajax(urls.users).then(function(users){
        ajax(urls.users).then(function(users){
            // ... 还是会形成回调地狱 promise 没有了意义
        }) 
    }) 
},function(error) {
    console.log(error)
})

就是你用了promise还发生了回调嵌套,没有用then链式调用。emm...

promise的链式调用

其实Promise最大的优势就是可以链式调用,这样就能最大程度的去避免回调嵌套。
每一个then方法他实际上都是在为上一个then返回的promise对象添加状态明确过后的回调,那这些promise会依次执行,这里添加的这些回调函数也就是从前到后依次执行。
例子:

function ajax(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('GET',url)
        xhr.responseType = 'json'
        xhr.onload = function() {
            if(this.status === 200) {
                resolve(this.response)
            }else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    }) 
}
let promise = ajax('api/user.json')
promise.then(function(value) {
    console.log("1111")
    return ajax('api/urls.json')
})//-> promise
.then(function(value) {
    console.log("2222")
})//-> promise
.then(function(value) {
    console.log("3333")
})//-> promise
.then(function(value) {
    console.log("4444")
})//-> promise
.then(function(value) {
    console.log("5555")
})

let promise2 = promise.then(function(res) {
    console.log(res)
},function(error) {
    console.log(error)
})
console.log(promise2 === promise) //false

总结:

  1. 每一个then方法都是在为上一个promise添加回调,
  2. promise的then方法会返回一个全新的promise对象(就可以使用链式调用的方式去调用then方法)
  3. 后面的then方法就是在为上一个then返回的promise注册回调
  4. 前面方法中回调函数的返回值 会作为后面then方法回调的参数
  5. 如果回调中返回的是一个promise对象,那后面的then方法的回调会等待它的结束

promise异常处理

  • Promise执行失败,会返回onRejected这个回调函数
  • 如果是在Promise执行的过程中,出现了异常,或者是我们手动抛出了一个异常,那onRejected也会被执行

我们也可以使用promise的cache方法来注册onRejected.
例子:

function ajax(url) {
    return new Promise(function(resolve, reject) {
        // foo() 可以捕获到异常
        var xhr = new XMLHttpRequest()
        xhr.open('GET',url)
        xhr.responseType = 'json'
        xhr.onload = function() {
            if(this.status === 200) {
                resolve(this.response)
            }else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    }) 
}
ajax('api/user.json') //1
    .then(function onFulfilled (res) {
        console.log("onFulfilled",res)
    },function onRejected (error) {
        console.log("onRejected",error)
    })
ajax('api/user.json') //2
    .then(function onFulfilled (res) {
        console.log("onFulfilled",res)
    })
    .catch(function onRejected (error) {
        console.log("onRejected",error)
    })

/* 
    1的onRejected 只能捕获第一个then方法返回的异常
    2 能捕获整个promise链条任何一个异常,都会往下传递直至被捕获
 */
//也能捕获到异常,catch 就是then方法的别名
// 相当于
// ajax('api/user.json')
//     .then(function onFulfilled (res) {
//         console.log("onFulfilled",res)
//     })
//     .then(undefined, function onRejected (error) {
//         console.log("onRejected",error)
//     })

promise的静态方法

promise.resolve()

快速的把一个值转换为一个promise对象

// promise.resolve() 作用就是快速把一个值转化为一个promise对象
Promise.resolve("foo")
    .then(function(value) {
        console.log(value)
    })
// 等价于
new Promise(function(resolve, reject) {
    resolve('foo')
})
// 如果primse.resolve()参数传一个promise对象 他会 返回原对象
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2) //true

// 如果我们传入的是个对象,有then方法(他是以额可以被then的对象),这样的对象耶可以作为promise来执行
Promise.resolve({
    then: function(onFulfilled, onRejected) {
        onFulfilled('foo1')
    }
})
.then(function(value) {
    console.log(value)
})
promise.reject()

快速的创建一个一定失败的promise对象.

// 无论传入什么参数都会作为 这个promise失败的原因
Promise.reject(new Error('rejected'))
    .catch (function(error) {
        console.log(error)
    })
//rejected

Promise并行处理

promise.all()

promise.all 允许我们按照异步代码调用的顺序得到异步代码执行的顺序, 接收数组,可以是promise对象 或普通值 , 返回是个promise对象 后面可以链接then,所有成功 promise.all才是成功 有一个失败就是失败。

// 返回一个新得promise对象,必须里面的都执行完成 才会完成,
// 必须成功执行 all才会成功执行,有一个任务失败了 这个promise以失败结束
var promise = Promise.all([
    ajax('/api/user.json'),
    ajax('/api/posts.json')
])
// 数组里包含每个异步任务执行的结果
promise.then(function(values) {
    console.log(values)
}).catch(function onRejected (error) {
    console.log("onRejected",error)
})

注意:promise.all() 等待所有的任务结束后才结束。

promise.race()

Promise.race() 跟着所有任务当中第一个任务一起结束。

const request = ajax('/api/posts.json')

const timeout = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
})

Promise.race({
    request,
    timeout
})
.then( value => {
    console.log(value)
})
.catch(error => {
    console.log(error)
})

promise执行时序

Promise 当中并没有任何的异步操作,那他的回调函数仍然会进入到回调队列当中去排队,也就是说我们必须要等待当前所有的同步代码执行完成,过后才会去执行 Promise 当中的回调。

js的执行次序 主线程->微任务队列->宏任务队列。
在主线程执行完后 会立即执行微任务队列(从头到尾)中间遇到宏观任务就加到宏观任务队列的尾部,执行完微队列,再执行宏任务。
而promise就是微任务,setTimeout等是宏任务。
看个例子:

console.log('global start')

Promise.resolve()
    .then(() => {
        console.log('promise')
    })

console.log('global end')

/**
 * global start
 * global end
 * promise
 * */ 
//=================================
console.log('global start')

Promise.resolve()
    .then(() => {
        console.log('promise 1')
    })
    .then(() => {
        console.log('promise 2')
    })
    .then(() => {
        console.log('promise 3')
    })
    .then(() => {
        console.log('promise 4')
    })

console.log('global end')

/**
 * global start
 * global end
 * promise 1
 * promise 2
 * promise 3
 * promise 4
 * */ 

//==============================
console.log('global start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve()
    .then(() => {
        console.log('promise 1')
    })
    .then(() => {
        console.log('promise 2')
    })
    .then(() => {
        console.log('promise 3')
    })
    .then(() => {
        console.log('promise 4')
    })

console.log('global end')

/**
 * global start
 * global end
 * promise 1
 * promise 2
 * promise 3
 * promise 4
 * setTimeout
 * */ 

完结撒花~😊

读书不觉已春深,一寸光阴一寸金

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容