Promise, популярное решение для асинхронного программирования js, является более научным и элегантным, чем древние функции обратного вызова. Он исходил от народа, а позже был завербован правительством.
Эта статья начнется с введения в использование, шаг за шагом разберется с промисом, исследует исходный код и, наконец, напишет промис в соответствии с официальной спецификацией.
Давайте сначала обнимем его, а потом разденем!
Я хочу сделать с тобой то, что весна делает с вишней.- Пабло Неруда
1. How Promise?
- Создать обещания
Во-первых, давайте посмотрим на использование обещания, Из названия видно, что это конструктор, поэтому мы должны создать его и получить экземпляр промиса p, мы печатаем p, чтобы увидеть
let p = new Promise
console.log(p) // TypeError: Promise resolver undefined is not a function
- параметр
Сообщение об ошибке сообщает нам, что Promise требует некоторых параметров. Здесь нам нужна функция (назовем ее исполнителем) в качестве параметра. Функция имеет два параметра — разрешение и отклонение. Эти два параметра также являются функциями (предоставляется движком js) , мы можем вызвать внутри промиса, когда асинхронная операция прошла успешно, вызвать разрешение, в противном случае отклонить.
let p =new Promise(function(resolve, reject){
if(/* 异步操作成功 */){
resolve(data)
}else{
reject(err)
}
})
- state
Теперь нам нужно знать важную концепцию, Promise имеет «состояния», а именно pending (состояние ожидания), выполнено (успешное состояние), отклонено (неудачное состояние), ожидание может быть преобразовано в выполненное или отклоненное, но выполненное и отклоненное не могут быть взаимно трансформировать.
- методы разрешения/отклонения
Метод разрешения может преобразовать ожидание в выполненное, а метод отклонения может преобразовать ожидание в отклоненное.
- затем метод
Передав две функции в качестве параметров методу then в примере с Promise, вы можете обеспечить обратный вызов при изменении состояния, первая функция — успешный обратный вызов, а вторая — обратный вызов с ошибкой.
p.then(function(data){ // resolve方法会将参数传进成功的回调
console.log(data)
}, function(err){ // reject方法会将失败的信息传进失败的回调
console.log(err)
})
взять каштанlet p = new Promise(function(resolve, reject){
setTimeout(function(){
let num = Math.random()
if (num > 0.5) {
resolve(num)
}else{
reject(num)
}
}, 1000)
})
p.then(function(num){
console.log('大于0.5的数字:', num)
},function(num){
console.log('小于等于0.5的数字', num)
})
// 运行第一次:小于等于0.5的数字 0.166162996031475
// 运行第二次:大于0.5的数字: 0.6591451548308984
...
В исполнителе промисов мы выполняем асинхронную операцию и вызываем callback-функцию успеха или неудачи, когда считаем нужным, и получаем нужные данные для следующей операции
- цепной вызов
Кроме того, каждый метод then будет возвращать новый экземпляр Promise (а не исходный), что позволяет методу then поддерживать цепочки вызовов и передавать параметры следующему затем через возвращаемое значение.
p.then(function(num){
return num
},function(num){
return num
}).then(function(num){
console.log('大于0.5的数字:', num)
},function(num){
console.log('小于等于0.5的数字', num)
})
- метод ловли
Метод catch эквивалентен .then(null, reject), а неудавшийся обратный вызов может быть указан напрямую (поддерживает получение ошибки предыдущего then)
- Promise.all()
Это может быть полезным способом единообразной обработки нескольких промисов.
Promise.all может заключать несколько экземпляров Promise в один экземпляр Promise.
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){
// 三个都成功则成功
}, function(){
// 只要有失败,则失败
})
Этот комбинированный экземпляр промиса имеет три состояния, как и обычные экземпляры.Вот решения состояния нескольких небольших промисов, которые его составляют. :
1. Когда все состояния Promise1, Promise2 и Promise3 успешны, тогда p является успешным; 2. Когда любой из Promise1, Promise2 и Promise3 находится в состоянии отказа, тогда p является состоянием отказа;
- Promise.race()
Подобно методу all, вы также можете обернуть несколько экземпляров Promise в новый экземпляр Promise.
Разница в том, что состояние большого промиса во всех определяется множеством маленьких промисов, а в гонке - состоянием первого маленького промиса, меняющего состояние.
- Promise.resolve()
Может генерировать успешное обещание
Promise.resolve('success') эквивалентно new Promise(function(resolve){resolve('success')})
- Promise.reject()
Может генерировать неудачное обещание
Promise.reject('Error') эквивалентно new Promise((resolve, reject) => reject('Error'))
Приведенное выше использование недостаточно подробно, следующий код будет легче понять.
2. Why Promise?
Возьмем в качестве примера ajax jquery (до версии @1.5.0 jquery также представила концепцию Promise позже) и посмотрите, как мы решали проблему асинхронности в прошлом.
$.get('url', {data: data}, function(result){
console.log('成功', result)// 成功的回调,result为异步拿到的数据
});
Выглядит нормально?
Представьте себе сценарий, когда нам нужно отправить несколько асинхронных запросов, причем запросы взаимосвязаны и взаимозависимы, без запроса 1 не будет запроса 2, без запроса 2 не будет запроса 3........
Тогда нам нужно написать
$.get('url', {data: data}, function(result1){
$.get('url', {data: result1}, function(result2){
$.get('url', {data: result2}, function(result3){
$.get('url', {data: result3}, function(result4){
......
$.get('url', {data: resultn}, function(resultn+1){
console.log('成功')
}
}
}
}
});
В этом случае мы попадем в легендарный ад обратного вызова, и уже никогда не сможем выбраться.
Такой код сложно поддерживать и отлаживать. Если возникает ошибка, она влияет на все тело.
Давайте посмотрим, как решается Promise, в качестве примера возьмем файл доступа fs в узле.
Сначала создайте три взаимозависимых текстовых файла.
1.текстовое содержимое:
2.txt
Содержимое 2.txt:
3.txt
3.текстовое содержимое:
完成
js-код:
let readFile = require('fs').readFile; // 加载node内置模块fs 利用readFile方法异步访问文件
function getFile(url){ // 创建一个读取文件方法
return new Promise(function(resolve, reject){ // 返回一个Promise对象
readFile(url, 'utf8', function(err,data){ // 读取文件
resolve(data) // 调用成功的方法
})
})
}
getFile('1.txt').then(function(data){ // then方法进行链式调用
console.log(data) // 2.txt
return getFile(data) //拿到了第一次的内容用来请求第二次
}).then(function(data){
console.log(data) // 3.txt
return getFile(data) //拿到了第二次的内容用来请求第三次
}).then(function(data){
console.log(data) // 完成
})
(Здесь нам не нужно сначала разбираться в коде, конкретное использование будет представлено ниже)
Это выглядит как еще несколько строк кода [смущенно], но мы создаем функцию чтения для возврата объекта Promise, а затем используем метод .then, который поставляется с Promise, чтобы вложенный асинхронный код выглядел синхронным, вот так, проблемы могут легко отлаживаться и модифицироваться.
3. What Promise?
Далее следует изюминка этой статьи, согласноPromiseA+(Официальный стандарт Promise) Реализуйте промис, используя около 180 строк кода. Функция может реализовать большинство функций (затем поймать все отказы от разрешения гонки). Здесь мы обсудим детали и проясним идеи шаг за шагом.
- Реализуйте методы разрешения, отклонения, затем методы и механизмы состояния.
Согласно методу использования, мы можем знать, что Promise — это конструктор, который должен принимать исполнителя, который предоставляет два метода: механизм внутреннего состояния и метод then в цепочке прототипов.
Начать лизать:
// myPromise
function Promise(executor){ //executor是一个执行器(函数)
let _this = this // 先缓存this以免后面指针混乱
_this.status = 'pending' // 默认状态为等待态
_this.value = undefined // 成功时要传递给成功回调的数据,默认undefined
_this.reason = undefined // 失败时要传递给失败回调的原因,默认undefined
function resolve(value) { // 内置一个resolve方法,接收成功状态数据
// 上面说了,只有pending可以转为其他状态,所以这里要判断一下
if (_this.status === 'pending') {
_this.status = 'resolved' // 当调用resolve时要将状态改为成功态
_this.value = value // 保存成功时传进来的数据
}
}
function reject(reason) { // 内置一个reject方法,失败状态时接收原因
if (_this.status === 'pending') { // 和resolve同理
_this.status = 'rejected' // 转为失败态
_this.reason = reason // 保存失败原因
}
}
executor(resolve, reject) // 执行执行器函数,并将两个方法传入
}
// then方法接收两个参数,分别是成功和失败的回调,这里我们命名为onFulfilled和onRjected
Promise.prototype.then = function(onFulfilled, onRjected){
let _this = this; // 依然缓存this
if(_this.status === 'resolved'){ // 判断当前Promise的状态
onFulfilled(_this.value) // 如果是成功态,当然是要执行用户传递的成功回调,并把数据传进去
}
if(_this.status === 'rejected'){ // 同理
onRjected(_this.reason)
}
}
module.exports = Promise // 导出模块,否则别的文件没法使用
Примечание: Названия вышеприведенного кода не случайны, как и onFulfilled и onRjected, оно строго соответствует спецификации Promise/A+, если не верите, посмотрите на картинку.
Таким образом, мы выполнили первый шаг, мы можем создать экземпляр Promise и использовать метод then, протестировать его.
let Promise = require('./myPromise') // 引入模块
let p = new Promise(function(resolve, reject){
resolve('test')
})
p.then(function(data){
console.log('成功', data)
},function(err){
console.log('失败', err)
})
// 成功 test
попробуй отклонить еще раз
let Promise = require('./myPromise') // 引入模块
let p = new Promise(function(resolve, reject){
reject('test')
})
p.then(function(data){
console.log('成功', data)
},function(err){
console.log('失败', err)
})
// 失败 test
Выглядит хорошо, но функция обратного вызова выполняется сразу и не может выполнять асинхронные операции, такие как это недопустимо
let p = new Promise(function(resolve, reject){
setTimeout(function(){
resolve(100)
}, 1000)
})
p.then(function(data){
console.log('成功', data)
},function(err){
console.log('失败', err)
})
// 不会输出任何代码
Причина в том, что мы оцениваем состояние успеха и состояние отказа только в функции then, а когда экземпляр новый, код в исполнителе будет выполняться немедленно, а код в setTimeout будет выполняться позже, т. е. затем метод выполняется. Когда состояние Promise не изменилось, оно все еще находится в состоянии ожидания, поэтому мы должны оценить состояние ожидания, и, поскольку код может быть асинхронным, мы должны найти способ кэшировать функцию обратного вызова и,Метод then можно использовать несколько раз., чтобы иметь возможность хранить несколько обратных вызовов, здесь мы используем массив.
- Асинхронный
Повесить два параметра на инстанс
_this.onResolvedCallbacks = []; // 存放then成功的回调
_this.onRejectedCallbacks = []; // 存放then失败的回调
Затем метод добавляет отложенное решение
if(_this.status === 'pending'){
// 每一次then时,如果是等待态,就把回调函数push进数组中,什么时候改变状态什么时候再执行
_this.onResolvedCallbacks.push(function(){ // 这里用一个函数包起来,是为了后面加入新的逻辑进去
onFulfilled(_this.value)
})
_this.onRejectedCallbacks.push(function(){ // 同理
onRjected(_this.reason)
})
}
Следующим шагом является добавление методов выполнения функций, хранящихся в массиве в методы разрешения и отклонения соответственно, и измените методы разрешения и отклонения выше.
function resolve(value) {
if (_this.status === 'pending') {
_this.status = 'resolved'
_this.value = value
_this.onResolvedCallbacks.forEach(function(fn){ // 当成功的函数被调用时,之前缓存的回调函数会被一一调用
fn()
})
}
}
function reject(reason) {
if (_this.status === 'pending') {
_this.status = 'rejected'
_this.reason = reason
_this.onRejectedCallbacks.forEach(function(fn){// 当失败的函数被调用时,之前缓存的回调函数会被一一调用
fn()
})
}
}
Теперь вы можете выполнять асинхронные задачи, затем вы можете несколько раз, и версия Promise для бедняков завершена,
- обрабатывать ошибки
Хотя приведенный выше код можно использовать, он не выдерживает проверки.Если реальный промис выдает ошибку в экземпляре, он должен перейти к отклонению:
new Promise(function(resolve, reject){
throw new Error('错误')
}).then(function(){
},function(err){
console.log('错误:', err)
})
// 错误: Error: 错误
Реализуем, идея очень простая, попробуй отловить когда экзекьютор выполняется
try{
executor(resolve, reject)
}catch(e){ // 如果捕获发生异常,直接调失败,并把参数穿进去
reject(e)
}
- Реализовать связанный вызов then (сложность)
Как упоминалось выше, then можно вызывать в цепочке, что делает Promise очень полезным.Конечно, эта часть исходного кода также более сложная.
Мы знаем, что jquery реализует связанные вызовы, возвращая this, но Promise не работает, почему бы и нет?
Подлинное Обещание — это рутина:
let p1 = new Promise(function(resolve, reject){
resolve()
})
let p2 = p1.then(function(data){ //这是p1的成功回调,此时p1是成功态
throw new Error('错误') // 如果这里抛出错误,p2应是失败态
})
p2.then(function(){
},function(err){
console.log(err)
})
// Error: 错误
Если это возвращается, то p2 совпадает с p1, и твердотельное состояние также такое же, но, как упоминалось выше, состояние успеха и состояние отказа Promise не могут быть преобразованы друг в друга, тогда эффект успеха p1 и p2 отказ не будет получен, но на самом деле это может произойти.
Таким образом, принцип метода then в Promise для достижения цепного вызова:возвращает новое обещание
В методе then сначала определите новый Promise с именем promise2 (официально указанный), затем оберните его обещанием2 в трех состояниях и используйте переменную x (указанную) для получения возвращаемого значения при вызове onFulfilled, trycatch Посмотрите на код , если это правильно, настройте разрешение, чтобы передать x, если есть ошибка, настройте отклонение, чтобы передать ошибку, и, наконец, верните promise2, чтобы вернуться, и вы можете делать цепные вызовы, но!
// 改动then
let promise2;
if (_this.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
// 可以凑合用,但是是有很多问题的
try {
let x = onFulfilled(_this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
}
if (_this.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
// 可以凑合用,但是是有很多问题的
try {
let x = onRjected(_this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
}
if(_this.status === 'pending'){
promise2 = new Promise(function (resolve, rejec
_this.onResolvedCallbacks.push(function(){
// 可以凑合用,但是是有很多问题的
try {
let x = onFulfilled(_this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
_this.onRejectedCallbacks.push(function(){
// 可以凑合用,但是是有很多问题的
try {
let x = onRjected(_this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
})
}
return promise2
Здесь я сначала объясню функцию x, а затем, почему бы и нет, x используется для получения возвращаемого значения последнего, например, этого
let p = new Promise(function(resolve, reject){
resolve(data)
})
p.then(function(data){
return xxx // 这里返回一个值
}, function(){
}).then(function(data){
console.log // 这里会接收到xxx
}, function(){
})
// 以上代码中第一次then的返回值就是源码内第一次调用onRjected的返回值,可以用一个x来接收
Затем сказал проблему, поэтому вышеизложенное кажется логичным, и действительно может быть сцеплено и принимать звонки, но мы пишем библиотеку, библиотека выдержит испытание, упомянута самая высокая отказоустойчивость, чтобы принять пользователя различные новые (сао) нечетные ( дан) операция, так называемая толерантность наи большая. Возможны следующие варианты:
1. Предыдущий затем вернул общее значение, например объекты строкового массива, нет проблем, просто передайте его следующему, метода сейчас достаточно.
2. Предыдущий затем вернул обещание, что является нормальной операцией и синтаксическим сахаром, предоставленным обещанием.Мы должны найти способ определить, что возвращается.
3. Предыдущий то вернул промис, у которого асинхронная операция, что само собой разумеется, то надо дождаться изменения его состояния, а потом переходить к следующей обработке.
4. Предыдущее, а затем возвращенное, было собственным промисом
var p1 = p.then(function(){// 这里得用var,let由于作用域的原因会报错undefined
return p1
})
5. Предыдущее возвращенное then было промисом, написанным кем-то другим. Это промис может быть обычным объектом с then, например {then:'hahaha'}, или может быть преднамеренно брошен неправильно в then (эта болезненная операция, которую мы надо это учитывать). Как он написал
let promise = {}
Object.defineProperty(promise,'then',{
value: function(){
throw new Error('报错气死你')
}
})
// 如果返回这东西,我们再去调then方法就肯定会报错了
6. При настройке разрешения передать еще один промис, и нам придется иметь дело с этим промисом.
p.then(function(data) {
return new Promise(function(resolve, reject) {
resolve(new Promise(function(resolve,reject){
resolve(1111)
}))
})
})
7. Можно настроить как разрешение, так и отклонение, а последнее нужно игнорировать.
8. Света то, в ней ничего не написано.
. .
Подожди, меня вырвет некоторое время. . .
Хорошо, давайте подкорректируем наше настроение и продолжим играть. На самом деле, многие из этой серии проблем связаны между собой. Если они соответствуют спецификациям, их можно решить гладко. Подключите приведенный выше код и сначала выполните три вещи.
1. Лучшее решение - задача 7. Если разрешение и отклонение не пройдено, мы дадим ему одно.
2. В официальной спецификации указано одно
Проще говоря, чтобы избежать проблем в тесте, onFulfilled и onRejected нужно выполнять асинхронно, поэтому мы позволяем им выполняться асинхронно.
3. Для вопросов 1-7 мы можем принять единое решение и определить функцию для оценки и обработки этой серии ситуаций.
Посмотрите на метод then еще раз
Promise.prototype.then = function (onFulfilled, onRjected) {
//成功和失败默认不传给一个函数,解决了问题8
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
throw err;
}
let _this = this;
let promise2; //返回的promise
if (_this.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
// 当成功或者失败执行时有异常那么返回的promise应该处于失败状态
setTimeout(function () {// 根据规范让那俩家伙异步执行
try {
let x = onFulfilled(_this.value);//这里解释过了
// 写一个方法统一处理问题1-7
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (_this.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRjected(_this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (_this.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
_this.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
_this.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRjected(_this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
Далее давайте посмотрим, как написать resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
// 接受四个参数,新Promise、返回值,成功和失败的回调
// 有可能这里返回的x是别人的promise
// 尽可能允许其他乱写
if (promise2 === x) { //这里应该报一个类型错误,来解决问题4
return reject(new TypeError('循环引用了'))
}
// 看x是不是一个promise,promise应该是一个对象
let called; // 表示是否调用过成功或者失败,用来解决问题7
//下面判断上一次then返回的是普通值还是函数,来解决问题1、2
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是promise {},看这个对象中是否有then方法,如果有then我就认为他是promise了
try {
let then = x.then;// 保存一下x的then方法
if (typeof then === 'function') {
// 成功
//这里的y也是官方规范,如果还是promise,可以当下一次的x使用
//用call方法修改指针为x,否则this指向window
then.call(x, function (y) {
if (called) return //如果调用过就return掉
called = true
// y可能还是一个promise,在去解析直到返回的是一个普通值
resolvePromise(promise2, y, resolve, reject)//递归调用,解决了问题6
}, function (err) { //失败
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 说明是一个普通值1
resolve(x); // 表示成功了
}
}
- пройти тест
PromiseA+ предоставляет тестовую библиотеку promises-aplus-tests, и метод ее использования четко объяснен на github.Откройте интерфейс адаптера:
Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
Из командной строки: promises-aplus-tests myPromise.js
Результаты после серии тестов
872 passing (18s)
Это доказывает, что наше обещание полностью соответствует спецификации!
- Другие методы
Кроме самого важного тогда метода, есть много других методов Promise, но они не сложные, я их здесь в свое время представлю.
// 捕获错误的方法,在原型上有catch方法,返回一个没有resolve的then结果即可
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}
// 解析全部方法,接收一个Promise数组promises,返回新的Promise,遍历数组,都完成再resolve
Promise.all = function (promises) {
//promises是一个promise的数组
return new Promise(function (resolve, reject) {
let arr = []; //arr是最终返回值的结果
let i = 0; // 表示成功了多少次
function processData(index, y) {
arr[index] = y;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y)
}, reject)
}
})
}
// 只要有一个promise成功了 就算成功。如果第一个失败了就失败了
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
// 生成一个成功的promise
Promise.resolve = function(value){
return new Promise(function(resolve,reject){
resolve(value);
})
}
// 生成一个失败的promise
Promise.reject = function(reason){
return new Promise(function(resolve,reject){
reject(reason);
})
}
Вывод: Promise — одно из лучших решений для асинхронности.Проанализировав исходный код, я получил глубокое понимание Promise и даже асинхронности js. Обещания есть уже давно, если вы этого не знаете, вы уже позади, поторопитесь и садитесь в машину. Мир программирования быстро меняется, и мы, как программисты, должны активно принимать изменения.
Добро пожаловать, чтобы добавить мой личный WeChat для всестороннего общения